Skip to content

[TRTLLM-14708][fix] Populate speculative-decoding request perf metrics in the PyTorch one-engine flow - #17082

Open
brnguyen2 wants to merge 5 commits into
NVIDIA:mainfrom
brnguyen2:feat/spec-dec-perf-metrics
Open

[TRTLLM-14708][fix] Populate speculative-decoding request perf metrics in the PyTorch one-engine flow#17082
brnguyen2 wants to merge 5 commits into
NVIDIA:mainfrom
brnguyen2:feat/spec-dec-perf-metrics

Conversation

@brnguyen2

@brnguyen2 brnguyen2 commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Description

Update (2026-07-31): #16752 has merged and this branch has been rebased onto post-merge main, so the stacked structure described here originally is gone. All 4 commits belong to this PR (the original fix plus three review-response commits); the full diff is the change to review. This is the follow-up promised in #16752: "AR reporting should return in a follow-up together with a fix populating those counters."

Problem

With SamplingParams(return_perf_metrics=True), per-request
output.outputs[0].request_perf_metrics.speculative_decoding
(total_accepted_draft_tokens, total_draft_tokens, acceptance_rate)
comes back zeroed in the PyTorch flow even when drafting ran, while
output.avg_decoded_tokens_per_iter flows correctly.

Root cause

The only writer of RequestPerfMetrics.speculativeDecoding is C++
LlmRequest::updateNumTokensPerIteration, which is called by the TRT engine
runtime (trtGptModelInflightBatching.cpp) and by the TRTLLMSampler
(sampler.py). The PyTorch flow with TorchSampler never calls it, so
createSerializedResult copies a zeroed section into every response.

Generality: this is PyTorch-flow generic (all spec modes, one- and
two-model), not specific to any drafter.
Acceptance counts exist per step
on the Python side (LlmRequest.py_num_accepted_draft_tokens) but were only
aggregated into ITERATION stats and the capped per-position arrays
(py_per_pos_*), never into the request's cumulative perf metrics.

Fix (Python-only, no cpp rebuild)

  • llm_request.py: LlmRequest gains exact cumulative
    py_total_draft_tokens / py_total_accepted_draft_tokens counters (unlike
    the per-pos arrays, not capped at MAX_SPEC_DECODE_POSITIONS).
  • py_executor.py (_handle_responses): accumulate them alongside the
    existing per-pos stats and attach (accepted, drafted) to the emitted
    response as LlmResult.spec_dec_totals. (The C++ Result is already
    serialized by create_serialized_result at that point, so the section
    cannot be written runtime-side without cpp changes.)
  • executor/result.py: GenerationResultBase._maybe_fill_spec_dec_perf_metrics
    backfills RequestPerfMetrics.speculative_decoding (totals + acceptance
    rate) from those totals when the section arrives empty. TRT-engine /
    TRTLLMSampler paths that already populate it runtime-side are detected
    (total_draft_tokens > 0) and left untouched.

This also fixes the existing record_stats fallback (executor/result.py)
that reads the same section for SPEC_DEC_* Prometheus metrics when the
per-pos arrays are absent.

Eval-side follow-up (the stacked part)

_log_spec_stats in evaluate/lm_eval.py (added by #16752) now also logs a
corpus acceptance-rate line — sum(accepted)/sum(drafted) over per-request
request_perf_metrics.speculative_decoding, gated on
total_draft_tokens > 0 — alongside the existing AL line.

Design decision: #16752 deliberately does not request perf metrics (zero
overhead), but AR needs return_perf_metrics=True. This PR opts requests
into perf metrics when TLLM_EVAL_SPEC_STATS=1 is set, i.e. the (small)
per-request perf-metrics overhead is bundled with explicitly asking for
spec-dec stats. The alternative (log AR only when the caller independently
enabled perf metrics) would make the AR line appear only with an extra,
undocumented knob.

Risks

  • spec_dec_totals accumulates in the same loop as the per-pos stats, using
    the same draft_len / py_num_accepted_draft_tokens per-iteration values;
    any bias in those (e.g. final-iteration draft handling) is shared with the
    already-validated per-pos stats.
  • The backfill only patches responses that carry request_perf_metrics
    (i.e. return_perf_metrics=True), matching C++ behavior.

Test Coverage

  • New CPU-only unit test tests/unittest/executor/test_spec_dec_perf_metrics.py:
    fills a zeroed section from totals (30/40 → AR 0.75), no-op without totals,
    no-op when the section is already populated runtime-side, no-op on
    non-positive drafted counts, and pickle round-trip (responses cross the
    worker/proxy IPC boundary pickled).
  • tests/unittest/others/test_lm_eval.py: new cases for the AR line
    (corpus summation, skip-when-no-drafting, AR-without-AL) and for the
    TLLM_EVAL_SPEC_STATS=1return_perf_metrics opt-in; existing AL tests
    updated so fake outputs carry explicit (or absent) perf-metrics counters.
  • GPU end-to-end: validated on a 16-GPU suffix-automaton spec-dec run
    (draft_len 2, TP16, 48 prompts): the AR line now prints
    (acceptance 661/5392 = 12.3%) alongside an unchanged AL line
    (tokens/step mean ~1.28); the pre-fix baseline printed the AL line only.

PR Checklist

  • PR title and description follow the repo conventions.
  • Test cases added/updated for the change.
  • pre-commit run --files <touched> clean.

Dev Engineer Review

The implementation correctly adds cumulative speculative-decoding counters to LlmRequest, transfers them through PyTorch executor responses, and backfills missing RequestPerfMetrics.speculative_decoding values. Existing populated metrics and TRT metrics remain unchanged.

The drafted-token count is clamped to max_draft_len, matching the C++ denominator for tree-based drafting. lm_eval reports corpus-level acceptance rate when TLLM_EVAL_SPEC_STATS=1. The acceptance-rate calculation and pickle round-trip behavior are covered.

The CPU test is included in tests/integration/test_lists/test-db/l0_cpu_x86.yml. No configuration or test-list format issues are reported.

QA Engineer Review

tests/unittest/executor/test_spec_dec_perf_metrics.py adds:

  • test_fills_zeroed_section_from_totals
  • test_noop_without_totals
  • test_noop_when_section_already_populated
  • test_noop_on_nonpositive_drafted
  • test_survives_pickle_roundtrip

The test file is covered by tests/integration/test_lists/test-db/l0_cpu_x86.yml.

tests/unittest/others/test_lm_eval.py adds or updates speculative-statistics tests for acceptance length, acceptance rate, missing-token handling, return_perf_metrics gating, request scheduling, partial scoring, multimodal scoring, and end-to-end execution. This file is covered by tests/integration/test_lists/test-db/l0_a10.yml.

Verdict: sufficient.

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62851 [ run ] triggered by Bot. Commit: 0354935 Link to invocation

@coderabbitai

coderabbitai Bot commented Jul 30, 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

The change propagates speculative-decoding token totals through PyTorch responses, backfills request performance metrics, and reports lm-eval acceptance rates. Tests cover metric persistence, evaluation controls, windowed generation, partial scoring, and multimodal output handling.

Changes

Evaluation and speculative metrics

Layer / File(s) Summary
Speculative metrics propagation
tensorrt_llm/_torch/pyexecutor/llm_request.py, tensorrt_llm/_torch/pyexecutor/py_executor.py, tensorrt_llm/executor/result.py, tests/unittest/executor/test_spec_dec_perf_metrics.py, tests/integration/test_lists/test-db/l0_cpu_x86.yml
Requests accumulate drafted and accepted-draft tokens. Responses carry totals. Generation results backfill missing metrics. Tests cover calculation, persistence, and configured execution.
Speculative evaluation statistics
tensorrt_llm/evaluate/lm_eval.py, tests/unittest/others/test_lm_eval.py
lm-eval enables performance metrics when requested and reports corpus-level acceptance rate with acceptance length.
Evaluation controls and scheduling
tests/unittest/others/test_lm_eval.py
Tests cover score tracking, partial scoring, bounded request windows, result ordering, error handling, and end-to-end evaluation.
Multimodal evaluation scoring
tests/unittest/others/test_lm_eval.py
Tests cover placeholder interleaving and scoring of post-processed multimodal output.

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

Sequence Diagram(s)

sequenceDiagram
  participant PyExecutor
  participant LlmResult
  participant GenerationResultBase
  participant LmEval
  PyExecutor->>LlmResult: Attach speculative-decoding totals
  LlmResult->>GenerationResultBase: Pass response totals
  GenerationResultBase->>LmEval: Expose request performance metrics
  LmEval->>LmEval: Aggregate acceptance rate
Loading

Possibly related PRs

Suggested labels: api-compatible

Suggested reviewers: qijune, emmaqiaoch, shixiaowei02

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% 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 follows the repository format and clearly identifies the fix for speculative-decoding request performance metrics in the PyTorch one-engine flow.
Description check ✅ Passed The description clearly explains the problem, root cause, implementation, risks, test coverage, and checklist status for the proposed changes.
✨ 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 (3)
tensorrt_llm/executor/result.py (1)

288-289: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Annotate the perf_metrics parameter.

The helper has a -> None return annotation but the parameter is untyped; tllm.RequestPerfMetrics is the concrete type flowing in from _handle_sequence.

♻️ Proposed annotation
-    def _maybe_fill_spec_dec_perf_metrics(self, perf_metrics) -> None:
+    def _maybe_fill_spec_dec_perf_metrics(
+            self, perf_metrics: tllm.RequestPerfMetrics) -> None:

As per coding guidelines: "Annotate every function, use None for non-returning functions, avoid Any and unnecessary type ignores".

🤖 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/executor/result.py` around lines 288 - 289, Update the
_maybe_fill_spec_dec_perf_metrics method signature to annotate perf_metrics as
tllm.RequestPerfMetrics, preserving its existing -> None return annotation and
implementation.

Source: Coding guidelines

tensorrt_llm/evaluate/lm_eval.py (1)

138-142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider narrowing the swallowed exception type.

The catch-all is deliberate (partial scoring must never fail the eval) and that intent is documented, but it also hides genuine bugs in the tracker itself — and the repo standard is to catch specific types. The realistic failure modes here are attribute/key/index/type/value errors from exotic task, filter, or metric shapes.

♻️ Proposed narrowing
-        except Exception as e:
+        except (AttributeError, IndexError, KeyError, TypeError,
+                ValueError) as e:
             logger.info(
                 f"Partial scoring disabled for this run ({type(e).__name__}: {e})"
             )
             self.disabled = True

If the blanket catch is intentional for third-party filter code, a short # noqa-style rationale noting the deliberate deviation would help future readers.

As per coding guidelines: "Catch specific exceptions instead of using broad or bare exception handling such as except:".

🤖 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/evaluate/lm_eval.py` around lines 138 - 142, In the
partial-scoring handling around the existing `self.disabled` assignment, replace
the broad `except Exception` with a tuple of the expected `AttributeError`,
`KeyError`, `IndexError`, `TypeError`, and `ValueError` exceptions. Preserve the
informational log and disabling behavior, and add a brief rationale comment only
if a broader catch remains necessary for third-party filter code.

Source: Coding guidelines

tests/unittest/others/test_lm_eval.py (1)

1493-1497: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard the optional lm_eval / datasets imports. Wrap these runtime imports in pytest.importorskip(...) so the E2E block skips cleanly when either package is missing; ConfigurableTask already accepts a custom_dataset callable here.

🤖 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/others/test_lm_eval.py` around lines 1493 - 1497, Update
_toy_task so its optional runtime dependencies are loaded through
pytest.importorskip for both datasets and lm_eval, allowing the E2E tests to
skip cleanly when either package is unavailable. Preserve the existing
ConfigurableTask custom_dataset setup and behavior.
🤖 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/evaluate/lm_eval.py`:
- Around line 64-70: Update the comment above SPEC_STATS_ENV_VAR to reflect that
acceptance-rate reporting is now supported, removing the outdated deferral
rationale and noting that speculative-decoding summaries include both acceptance
length and acceptance rate when performance metrics are enabled by
_get_sampling_params.

In `@tests/unittest/executor/test_spec_dec_perf_metrics.py`:
- Around line 1-18: Mark the test module containing the GenerationResultBase
speculative-decoding metrics tests with pytest.mark.cpu_only so it is selected
by the CPU-only test stages. Apply the marker at module scope, using the
existing pytest import and preserving the current test logic.

---

Nitpick comments:
In `@tensorrt_llm/evaluate/lm_eval.py`:
- Around line 138-142: In the partial-scoring handling around the existing
`self.disabled` assignment, replace the broad `except Exception` with a tuple of
the expected `AttributeError`, `KeyError`, `IndexError`, `TypeError`, and
`ValueError` exceptions. Preserve the informational log and disabling behavior,
and add a brief rationale comment only if a broader catch remains necessary for
third-party filter code.

In `@tensorrt_llm/executor/result.py`:
- Around line 288-289: Update the _maybe_fill_spec_dec_perf_metrics method
signature to annotate perf_metrics as tllm.RequestPerfMetrics, preserving its
existing -> None return annotation and implementation.

In `@tests/unittest/others/test_lm_eval.py`:
- Around line 1493-1497: Update _toy_task so its optional runtime dependencies
are loaded through pytest.importorskip for both datasets and lm_eval, allowing
the E2E tests to skip cleanly when either package is unavailable. Preserve the
existing ConfigurableTask custom_dataset setup and 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: f8258d1d-ba63-4621-becb-f4ee03c5322c

📥 Commits

Reviewing files that changed from the base of the PR and between c083da6 and 0354935.

📒 Files selected for processing (7)
  • tensorrt_llm/_torch/pyexecutor/llm_request.py
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tensorrt_llm/evaluate/lm_eval.py
  • tensorrt_llm/executor/result.py
  • tests/integration/test_lists/test-db/l0_a10.yml
  • tests/unittest/executor/test_spec_dec_perf_metrics.py
  • tests/unittest/others/test_lm_eval.py

Comment thread tensorrt_llm/evaluate/lm_eval.py
Comment thread tests/unittest/executor/test_spec_dec_perf_metrics.py
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62851 [ run ] completed with state FAILURE. Commit: 0354935
/LLM/main/L0_MergeRequest_PR pipeline #50974 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

@brnguyen2
brnguyen2 force-pushed the feat/spec-dec-perf-metrics branch from 0354935 to 5d06271 Compare July 30, 2026 23:39
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

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

🧹 Nitpick comments (1)
tensorrt_llm/executor/result.py (1)

288-289: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Annotate perf_metrics.

The helper is otherwise fully typed; perf_metrics is a tllm.RequestPerfMetrics.

♻️ Proposed change
-    def _maybe_fill_spec_dec_perf_metrics(self, perf_metrics) -> None:
+    def _maybe_fill_spec_dec_perf_metrics(
+            self, perf_metrics: tllm.RequestPerfMetrics) -> 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/executor/result.py` around lines 288 - 289, Update the
_maybe_fill_spec_dec_perf_metrics method signature to annotate perf_metrics as
tllm.RequestPerfMetrics, while preserving its existing None return annotation
and implementation.

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.

Nitpick comments:
In `@tensorrt_llm/executor/result.py`:
- Around line 288-289: Update the _maybe_fill_spec_dec_perf_metrics method
signature to annotate perf_metrics as tllm.RequestPerfMetrics, while preserving
its existing None return annotation and implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 4fa6fc08-2d56-4d07-aef6-2d73eec748e0

📥 Commits

Reviewing files that changed from the base of the PR and between 0354935 and 5d06271.

📒 Files selected for processing (7)
  • tensorrt_llm/_torch/pyexecutor/llm_request.py
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tensorrt_llm/evaluate/lm_eval.py
  • tensorrt_llm/executor/result.py
  • tests/integration/test_lists/test-db/l0_a10.yml
  • tests/unittest/executor/test_spec_dec_perf_metrics.py
  • tests/unittest/others/test_lm_eval.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/integration/test_lists/test-db/l0_a10.yml

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62880 [ run ] triggered by Bot. Commit: 5d06271 Link to invocation

@BowenFu

BowenFu commented Jul 31, 2026

Copy link
Copy Markdown

Reviewed 5d06271f4 only, as you asked. The shape is right — the root cause is correctly identified (updateNumTokensPerIteration is the sole C++ writer and TorchSampler never calls it), the fix is additive on every existing path, and the "already populated" guard on total_draft_tokens > 0 correctly leaves the TRT-engine / TRTLLMSampler paths alone. I also checked the acceptance_rate formula against C++: llmRequest.cpp:207-209 computes totalAcceptedDraftTokens / totalDraftTokens in createResult, so your backfill matches it exactly rather than inventing a second definition. The pickle round-trip test is a good instinct given the section crosses the worker/proxy boundary.

Two things.

The new test file never runs in CI. tests/unittest/executor/ is enumerated file-by-file in the test-db lists — l0_a10.yml:153-157, l0_a100.yml:26-28, l0_b200.yml:183, l0_cpu_x86.yml:16-19, l0_h100.yml:356 — there is no directory-level selector. test_spec_dec_perf_metrics.py isn't added to any of them; the l0_a10.yml hunk in this PR is #16752's test_lm_eval.py line. It's CPU-only and needs nothing but the bindings, so it should slot in next to the other unittest/executor/* entries. Same wiring point I raised on #16752, and worth catching before the rebase rather than after.

The two paths don't accumulate the denominator the same way. C++ does totalDraftTokens += std::min(getNumDraftTokens(), maxDraftPathLen) (llmRequest.h:1321), while py_executor.py does py_total_draft_tokens += draft_len with draft_len = len(request.py_draft_tokens) and no clamp. For a linear draft chain these agree, which I expect is every spec mode the PyTorch flow supports today — so I don't think this is a live bug. But the clamp exists in C++ specifically because tree-based drafting carries more draft tokens than the max accepted path length, and the whole point of this PR is that both paths now fill the same field. If the PyTorch flow is guaranteed to be a chain, a one-line comment saying so would keep the two definitions from drifting; if it isn't, the clamp should be mirrored.

Holding my approval mainly because this is stacked and will be rebased once #16752 lands — the diff I'd be approving isn't the one that merges. Happy to re-review promptly after the retarget.

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62880 [ run ] completed with state FAILURE. Commit: 5d06271
/LLM/main/L0_MergeRequest_PR pipeline #51002 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

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62925 [ run ] triggered by Bot. Commit: 5d06271 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62925 [ run ] completed with state SUCCESS. Commit: 5d06271
/LLM/main/L0_MergeRequest_PR pipeline #51047 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

…s in the PyTorch one-engine flow

With SamplingParams(return_perf_metrics=True), the
request_perf_metrics.speculative_decoding section arrived zeroed in the
PyTorch flow even when drafting ran: its only writer is C++
LlmRequest::updateNumTokensPerIteration, which the TRT engine runtime and
TRTLLMSampler call but the TorchSampler path never does. Accumulate exact
per-request draft/accepted totals in the PyTorch executor, attach them to
the response, and backfill the serialized section client-side; the
runtime-populated paths are left untouched.

Also restore the acceptance-rate (AR) line to the TLLM_EVAL_SPEC_STATS
lm-eval summary, deferred by the parent change until these counters were
populated; enabling the stats now also opts requests into perf metrics.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@brnguyen2
brnguyen2 force-pushed the feat/spec-dec-perf-metrics branch from 5d06271 to 486171b Compare July 31, 2026 07:49
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62994 [ run ] triggered by Bot. Commit: 486171b Link to invocation

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

Hi @BowenFu , #16752 has merged, mind taking a second look at this PR?

@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

🤖 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/unittest/executor/test_spec_dec_perf_metrics.py`:
- Line 1: Add the repository-standard NVIDIA copyright header at the top of the
new test module, before its module docstring, while leaving the existing test
content unchanged.
- Around line 39-46: Update the no-op tests around test_noop_without_totals and
test_noop_on_nonpositive_drafted to assert all three speculative-decoding
metrics: total_accepted_draft_tokens, total_draft_tokens, and acceptance_rate.
Add the missing assertions in every no-op branch while preserving the expected
zero/default values established by the 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: d2a9089d-5e5c-4b85-b36d-60e7617847f5

📥 Commits

Reviewing files that changed from the base of the PR and between 5d06271 and 486171b.

📒 Files selected for processing (6)
  • tensorrt_llm/_torch/pyexecutor/llm_request.py
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tensorrt_llm/evaluate/lm_eval.py
  • tensorrt_llm/executor/result.py
  • tests/unittest/executor/test_spec_dec_perf_metrics.py
  • tests/unittest/others/test_lm_eval.py
🚧 Files skipped from review as they are similar to previous changes (5)
  • tensorrt_llm/executor/result.py
  • tensorrt_llm/evaluate/lm_eval.py
  • tensorrt_llm/_torch/pyexecutor/llm_request.py
  • tensorrt_llm/_torch/pyexecutor/py_executor.py
  • tests/unittest/others/test_lm_eval.py

Comment thread tests/unittest/executor/test_spec_dec_perf_metrics.py
Comment thread tests/unittest/executor/test_spec_dec_perf_metrics.py
@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #62994 [ run ] completed with state FAILURE. Commit: 486171b
/LLM/main/L0_MergeRequest_PR pipeline #51101 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

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
C++ LlmRequest::updateNumTokensPerIteration accumulates
totalDraftTokens += min(getNumDraftTokens(), maxDraftPathLen) so that
tree-based drafting counts at most one path per step in the
acceptance-rate denominator. The PyTorch accumulation added by this PR
used the unclamped draft token count, which agrees for linear chains
but would over-count for tree drafters. Clamp to max_draft_len (the
per-path depth; max_total_draft_tokens is the tree size) to keep both
paths on the same definition.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
The SPEC_STATS_ENV_VAR comment still described AR reporting as deferred
to a follow-up; this PR is that follow-up.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

@BowenFu Both points addressed, thanks — the branch is now rebased onto main post-#16752, so the diff you'd approve is the one that merges.

CI wiring (ae4ff3972): test_spec_dec_perf_metrics.py is added to l0_cpu_x86.yml next to the other unittest/executor/* entries — it's CPU-only (bindings + executor/result.py, no engine), so the CPU list is its natural home rather than a GPU stage. Verified green before wiring: the full file (5 tests) plus test_lm_eval.py (92 tests) pass in a representative environment, 97/97.

Denominator clamp (60e5938ec): mirrored rather than documented. You're right that the chain guarantee doesn't hold — the PyTorch flow does have tree drafters (eagle3_dynamic_tree, mtp_dynamic_tree), where len(py_draft_tokens) can reach max_total_draft_tokens while at most max_draft_len (the per-path depth, i.e. the Python analogue of getMaxDraftPathLen()) can be accepted per step. The accumulation now does min(draft_len, self.max_draft_len); PyExecutor.max_draft_len is set once in __init__ from spec_config.max_draft_len, so it's the static per-path max like the C++ model-config value, and the clamp is a no-op for linear chains.

Also refreshed the now-stale "AR reporting is deferred" comment above SPEC_STATS_ENV_VAR (45ead422b).

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63030 [ run ] triggered by Bot. Commit: 45ead42 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63030 [ run ] completed with state SUCCESS. Commit: 45ead42
/LLM/main/L0_MergeRequest_PR pipeline #51135 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

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63043 [ run ] triggered by Bot. Commit: 45ead42 Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63043 [ run ] completed with state FAILURE. Commit: 45ead42
/LLM/main/L0_MergeRequest_PR pipeline #51147 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

Comment thread tensorrt_llm/_torch/pyexecutor/py_executor.py Outdated
Address review: py_draft_tokens is not a valid denominator at
response-handling time. In the one-model flow (SpecSamplerBase),
update_requests has already replaced it with the NEXT step's buffer, and
the request's prefill iteration carries dummy draft tokens; in the
drafter flows, pad_draft_tokens_for_cuda_graph has padded it to the
static max. All of these inflated the drafted count and biased the
acceptance rate (and the per-position stats) low. _handle_responses
also re-read the same stale counters for every active request each
iteration, double-counting steps for requests that sat unscheduled.

Samplers now write the paired denominator at the same place they write
py_num_accepted_draft_tokens:

- SpecSamplerBase.sample_async snapshots per-request draft counts into
  the sample state (before dummy-token insertion; preferring the
  pre-padding count the drafter recorded), and update_requests copies
  them onto the request as py_num_draft_tokens_verified.
- TorchSampler pairs against the pre-padding count recorded by
  pad_draft_tokens_for_cuda_graph (py_draft_tokens_effective_len);
  py_rewind_len keeps using the padded length, since padding occupies
  KV cache.

Accumulation moves from _handle_responses into the executor right after
Sampler.update_requests (_accumulate_spec_dec_stats), consuming
py_num_draft_tokens_verified exactly once per verified step. This fixes
the cumulative totals and the per-position arrays alike.

New tests in tests/unittest/executor/test_spec_dec_stats_pairing.py pin
the pairing: verified-count-vs-replaced-buffer, consume-once semantics,
prefill steps counting nothing, the tree-drafting clamp, and the
drafter's pre-padding recording. Wired into l0_a10.yml rather than the
CPU-only list: the tests are GPU-free but import py_executor, which no
cpu_only-stage test currently does.

Signed-off-by: Brian Nguyen <brnguyen@nvidia.com>
@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63142 [ run ] triggered by Bot. Commit: 930a45c Link to invocation

@tensorrt-cicd

Copy link
Copy Markdown
Collaborator

PR_Github #63142 [ run ] completed with state FAILURE. Commit: 930a45c
/LLM/main/L0_MergeRequest_PR pipeline #51230 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

@brnguyen2

Copy link
Copy Markdown
Collaborator Author

/bot run

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.

7 participants