Skip to content

[docs] Add VLA full hidden_states export example - #17420

Open
hous-ailab wants to merge 1 commit into
NVIDIA:mainfrom
hous-ailab:add-vla-hidden-states-example
Open

[docs] Add VLA full hidden_states export example#17420
hous-ailab wants to merge 1 commit into
NVIDIA:mainfrom
hous-ailab:add-vla-hidden-states-example

Conversation

@hous-ailab

@hous-ailab hous-ailab commented Aug 7, 2026

Copy link
Copy Markdown

Summary

Adds examples/vla_hidden_states_export/ - a guide for exporting full 3D hidden_states from TensorRT-LLM for VLA (Vision-Language-Action) models.

Fixes #4414

Background

VLA models (Orion, OpenVLA, RT-2) use the LLM's hidden states as input to downstream task heads, rather than for token generation:

  • Standard LLM: input -> LLM -> logits -> tokens -> text
  • VLA model: images+text -> LLM -> hidden_states -> planning head -> trajectory

The downstream head needs the hidden state at a specific token position (e.g. a "waypoint" token), which requires the complete [batch, seq_len, hidden_dim] tensor - not just the last token.

Why existing APIs fall short

  • gather_last_token_logits: compresses 3D to 2D, only last token survives
  • additional_model_outputs (v1.1+): standard DecoderModelForCausalLM.forward only returns logits; hidden_states is consumed by LogitsProcessor before it can be exposed
  • SaveHiddenStatesDecodingConfig: saves to disk for offline EAGLE3 training, not usable at inference

Solutions

Solution A: TRT backend mark_output (v0.7-v0.21, verified)

Insert mark_output before gather_last_token_logits:

hidden_states.mark_output('full_hidden_states', self.config.dtype)
hidden_states = gather_last_token_logits(...)
lm_logits = self.lm_head(hidden_states)

Compatibility verified across v0.7.0 to v0.21.0 (15 versions).

Reading at inference:

full_hs = model.session.debug_buffer["full_hidden_states"]
ego_feature = full_hs[0, waypoint_idx, :]

Solution B: PyTorch backend forward modification (v1.x)

Attach the raw 3D tensor before LogitsProcessor compresses it, enabling additional_model_outputs=["hidden_states"].

Verification (Solution A)

Tested on Orion VLA model (ICCV'25) with TRT-LLM v0.13.0:

  • Engine output: [1, 599, 4096] (full 3D)
  • Hidden_states CosSim vs PyTorch: 0.9994 (INT8)
  • End-to-end plan_L2_1s: 0.686 (PyTorch: 0.690)
  • Platforms: RTX 4090 + AGX Orin

Notes

  • Solution B is based on v1.x source code analysis, not yet runtime-tested
  • Happy to adjust based on maintainer feedback on preferred integration approach

Dev Engineer Review

  • Added documentation and scripts for exporting full 3D hidden states from TensorRT-LLM.
  • Documented TRT-LLM v0.x support through mark_output before gather_last_token_logits.
  • Documented a PyTorch v1.x approach through additional_model_outputs.
  • Added inference code that validates engine I/O, retrieves full_hidden_states, and extracts token features.
  • Added version guidance, patches, Orion verification results, and usage instructions.
  • The PyTorch approach remains source-based and has not been runtime-tested.
  • No public API declarations, configuration files, or test-list files were changed.
  • Review focus: verify CUDA buffer handling, supported tensor ranks, sequence-length assumptions, API compatibility, and error handling in HiddenStatesEngine.
  • Verdict: needs follow-up because the new inference path and PyTorch integration are not fully runtime-validated.

QA Engineer Review

  • Added test_engine_has_full_hidden_states.
  • Added test_hidden_states_is_3d.
  • Added test_token_extraction.
  • Added a command-line test entry point with simulated extraction fallback.
  • These tests are not listed in tests/integration/test_lists/ test-db or QA files.
  • The engine tests require a suitable TensorRT-LLM engine and are not standard CI tests.
  • Verdict: needs follow-up.

@hous-ailab
hous-ailab marked this pull request as ready for review August 7, 2026 14:36
@hous-ailab
hous-ailab requested a review from a team as a code owner August 7, 2026 14:36
@coderabbitai

coderabbitai Bot commented Aug 7, 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: ef78452b-2fef-4aef-b27b-193f8ceec03e

📥 Commits

Reviewing files that changed from the base of the PR and between 329ffd2 and d5deab9.

📒 Files selected for processing (6)
  • examples/vla_hidden_states_export/README.md
  • examples/vla_hidden_states_export/export_hidden_states.py
  • examples/vla_hidden_states_export/inference_python.py
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch
  • examples/vla_hidden_states_export/tests/test_hidden_states.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/vla_hidden_states_export/export_hidden_states.py
  • examples/vla_hidden_states_export/README.md
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch

Walkthrough

This change adds VLA hidden-state export support through backend patches, engine build instructions, Python inference, validation tests, and documentation.

Changes

VLA hidden-state export

Layer / File(s) Summary
Hidden-state output contract
examples/vla_hidden_states_export/patches/*, examples/vla_hidden_states_export/README.md
The patches describe TensorRT and PyTorch methods for exposing full hidden states. The README documents output shapes and retrieval.
Engine build and output verification
examples/vla_hidden_states_export/export_hidden_states.py, examples/vla_hidden_states_export/README.md
The build guide documents v0.x trtllm-build usage, model patching, and verification of full_hidden_states.
Inference retrieval and validation
examples/vla_hidden_states_export/inference_python.py, examples/vla_hidden_states_export/tests/test_hidden_states.py
The inference example loads an engine, executes token IDs, extracts waypoint features, and manages CUDA buffers. Tests validate output presence, rank, and token extraction.

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

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant trtllm_build
  participant TensorRT_Engine
  participant HiddenStatesEngine
  participant ValidationTests
  CLI->>trtllm_build: build engine with patched model
  trtllm_build->>TensorRT_Engine: write rank0.engine
  HiddenStatesEngine->>TensorRT_Engine: load and execute input_ids
  TensorRT_Engine-->>HiddenStatesEngine: return full_hidden_states
  HiddenStatesEngine-->>CLI: return hidden states and waypoint feature
  ValidationTests->>TensorRT_Engine: verify output and shape
Loading

Suggested reviewers: zhenhuaw-me

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the addition of a VLA hidden-state export example.
Description check ✅ Passed The description explains the problem, solutions, verification results, limitations, and linked issue, but omits the template checklist sections.
Linked Issues check ✅ Passed The changes directly address issue #4414 by documenting and demonstrating methods to return full hidden states from TensorRT-LLM.
Out of Scope Changes check ✅ Passed The documentation, example scripts, patches, and tests are within the stated scope of exporting and validating VLA hidden states.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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: 10

🤖 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 `@examples/vla_hidden_states_export/export_hidden_states.py`:
- Line 35: All functions in the VLA hidden-states export example lack required
type annotations. Add complete parameter and return annotations to
build_engine_v0x and main in
examples/vla_hidden_states_export/export_hidden_states.py (lines 35-35 and
66-66), HiddenStatesEngine.__init__, infer_and_extract_hidden_states, and main
in examples/vla_hidden_states_export/inference_python.py (lines 29-29, 57-57,
and 124-124), and test_engine_has_full_hidden_states, test_hidden_states_is_3d,
test_token_extraction, and main in
examples/vla_hidden_states_export/tests/test_hidden_states.py (lines 19-19,
41-41, 58-58, and 72-72).

In `@examples/vla_hidden_states_export/inference_python.py`:
- Around line 129-138: Validate args.waypoint_idx against the dummy input
sequence length before infer_and_extract_hidden_states indexes full_hs,
rejecting values outside the valid range instead of using the default 32000 with
599 tokens. In examples/vla_hidden_states_export/tests/test_hidden_states.py
lines 60-69, replace modulo-based indexing with an explicit out-of-range test
that verifies invalid waypoint indices are rejected.
- Around line 80-103: Update the hidden-state and logits buffer handling around
full_hidden_states to derive each tensor’s dtype from engine.get_tensor_dtype(),
convert it to the corresponding NumPy dtype, and allocate using that dtype’s
item size. Use the same dtype when creating host arrays and reshaping copied
data, preserving correct fp16 and fp32 behavior for all outputs.
- Around line 72-98: Update the inference setup around the execution flow to set
runtime shapes for every tensor in self.input_names before querying any output
shapes, and bind each required input tensor rather than only input_ids. Remove
the set_tensor_shape call for full_hidden_states, validate that TensorRT shape
inference succeeds and that execute_async_v3 returns true, then synchronize only
after successful execution.

In `@examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch`:
- Line 1: Add the repository-standard NVIDIA copyright header, using the year of
the latest meaningful modification, at the beginning of each affected file:
examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch:1-1 and
modeling_utils_v1x.patch:1-1 before the patch descriptions;
export_hidden_states.py:1-1, inference_python.py:1-1, and
tests/test_hidden_states.py:1-1 before their module docstrings.
- Around line 25-33: Align the hidden-state output contract across all affected
sites: in examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch
lines 25-33, either export a padded 3D tensor or explicitly rename and document
the packed 2D output; in export_hidden_states.py lines 41-44, disable
remove_input_padding when 3D output is required; in README.md lines 26-50,
document packed output if retained; in inference_python.py lines 80-111, map
packed-token offsets per request; and in tests/test_hidden_states.py lines
41-55, assert the rank corresponding to the selected build mode.

In `@examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch`:
- Around line 26-32: Define a v1.x additional-output bridge that consumes
DecoderModelForCausalLM.forward() tensors, converts context outputs to [tokens,
hidden_dim] and generation outputs to [tokens, beam_width, hidden_dim], and
publishes them through the output dictionary consumed by HandleAdditionalOutputs
instead of assigning unused _additional_outputs. In
examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch lines 26-32,
implement this bridge for hidden_states and preserve the SamplingParams
additional-output contract; update examples/vla_hidden_states_export/README.md
lines 57-76 to document both supported shapes and add batched context and
generation runtime coverage.

In `@examples/vla_hidden_states_export/README.md`:
- Around line 7-10: Add the Markdown language identifier text to the fenced
diagram code block in the README, preserving the existing diagram content
unchanged.
- Around line 10-12: The documented invocation must match the parser: update
examples/vla_hidden_states_export/README.md lines 10-12 to use the supported
--engine_path argument and remove unsupported --engine_dir, --input_ids, and
--hidden_dim flags. In examples/vla_hidden_states_export/inference_python.py
lines 124-138, add corresponding parser arguments only if configurable engine
input is intended; otherwise leave this site unchanged because the README should
reflect the existing dummy-input behavior.

In `@examples/vla_hidden_states_export/tests/test_hidden_states.py`:
- Around line 19-25: Update the engine-dependent tests around
test_engine_has_full_hidden_states to obtain engine_path through a defined
pytest fixture or command-line option, and apply the same change to the related
tests. When no engine path is configured, skip these tests cleanly instead of
allowing fixture setup to fail.
🪄 Autofix

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: 195c7d8c-f621-43e4-ae85-ddcf642e8ce3

📥 Commits

Reviewing files that changed from the base of the PR and between 6c055a6 and ecf4f76.

📒 Files selected for processing (6)
  • examples/vla_hidden_states_export/README.md
  • examples/vla_hidden_states_export/export_hidden_states.py
  • examples/vla_hidden_states_export/inference_python.py
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch
  • examples/vla_hidden_states_export/tests/test_hidden_states.py

import sys


def build_engine_v0x(model_dir, output_dir, dtype="fp16"):

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add type annotations to every function.

The Python coding guidelines require annotations for every function, including constructors, test functions, and main functions.

  • examples/vla_hidden_states_export/export_hidden_states.py#L35-L35: annotate build_engine_v0x.
  • examples/vla_hidden_states_export/export_hidden_states.py#L66-L66: annotate main.
  • examples/vla_hidden_states_export/inference_python.py#L29-L29: annotate HiddenStatesEngine.__init__.
  • examples/vla_hidden_states_export/inference_python.py#L57-L57: annotate infer_and_extract_hidden_states.
  • examples/vla_hidden_states_export/inference_python.py#L124-L124: annotate main.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L19-L19: annotate test_engine_has_full_hidden_states.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L41-L41: annotate test_hidden_states_is_3d.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L58-L58: annotate test_token_extraction.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L72-L72: annotate main.
📍 Affects 3 files
  • examples/vla_hidden_states_export/export_hidden_states.py#L35-L35 (this comment)
  • examples/vla_hidden_states_export/export_hidden_states.py#L66-L66
  • examples/vla_hidden_states_export/inference_python.py#L29-L29
  • examples/vla_hidden_states_export/inference_python.py#L57-L57
  • examples/vla_hidden_states_export/inference_python.py#L124-L124
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L19-L19
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L41-L41
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L58-L58
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L72-L72
🤖 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 `@examples/vla_hidden_states_export/export_hidden_states.py` at line 35, All
functions in the VLA hidden-states export example lack required type
annotations. Add complete parameter and return annotations to build_engine_v0x
and main in examples/vla_hidden_states_export/export_hidden_states.py (lines
35-35 and 66-66), HiddenStatesEngine.__init__, infer_and_extract_hidden_states,
and main in examples/vla_hidden_states_export/inference_python.py (lines 29-29,
57-57, and 124-124), and test_engine_has_full_hidden_states,
test_hidden_states_is_3d, test_token_extraction, and main in
examples/vla_hidden_states_export/tests/test_hidden_states.py (lines 19-19,
41-41, 58-58, and 72-72).

Source: Coding guidelines

Comment on lines +72 to +98
# Input: input_ids
input_ids_np = np.ascontiguousarray(input_ids.astype(np.int32))
d_input = cuda.mem_alloc(input_ids_np.nbytes)
cuda.memcpy_htod(d_input, input_ids_np)
self.context.set_tensor_address("input_ids", int(d_input))
buffers["input_ids"] = d_input

# Output: full_hidden_states
hs_shape = tuple(self.context.get_tensor_shape("full_hidden_states"))
hs_size = int(np.prod([max(1, s) for s in hs_shape]))
hs_dtype = np.float16 # fp16 engine
d_hs = cuda.mem_alloc(hs_size * 2) # fp16 = 2 bytes
self.context.set_tensor_shape("full_hidden_states", hs_shape)
self.context.set_tensor_address("full_hidden_states", int(d_hs))
buffers["full_hidden_states"] = d_hs

# Output: logits (standard)
if "logits" in self.output_names:
logits_shape = tuple(self.context.get_tensor_shape("logits"))
logits_size = int(np.prod([max(1, s) for s in logits_shape]))
d_logits = cuda.mem_alloc(logits_size * 4)
self.context.set_tensor_address("logits", int(d_logits))
buffers["logits"] = d_logits

# --- Execute ---
self.context.execute_async_v3(self.stream.handle)
self.stream.synchronize()

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 | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python - <<'PY'
import tensorrt as trt

context = trt.IExecutionContext
print("TensorRT:", trt.__version__)
print("set_input_shape:", hasattr(context, "set_input_shape"))
print("set_tensor_shape:", hasattr(context, "set_tensor_shape"))
print("infer_shapes:", hasattr(context, "infer_shapes"))
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 278


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="examples/vla_hidden_states_export/inference_python.py"
printf '%s\n' "== target file outline =="
ast-grep outline "$file" || true
printf '%s\n' "== target lines =="
sed -n '1,150p' "$file"
printf '%s\n' "== related tensor setup and engine construction =="
rg -n -C 4 'input_names|output_names|set_input_shape|set_tensor_shape|get_tensor_shape|execute_async_v3|full_hidden_states|logits' examples/vla_hidden_states_export
printf '%s\n' "== all references to inference class and input names =="
rg -n -C 3 'class |input_names|input_ids|pixel|image|vision|inference_python' examples/vla_hidden_states_export

Repository: NVIDIA/TensorRT-LLM

Length of output: 44188


🌐 Web query:

TensorRT Python IExecutionContext set_input_shape set_tensor_shape get_tensor_shape execute_async_v3 output shape inference documentation

💡 Result:

In TensorRT's Python API, managing dynamic shapes and executing inference with execute_async_v3 involves a standardized workflow within the IExecutionContext interface [1][2]. Key methods for dynamic shape management: - context.set_input_shape(name, shape): Sets the input dimensions for a specific tensor before inference [3][2]. This must be performed after selecting an appropriate optimization profile [3][4]. - context.get_tensor_shape(name): Returns the runtime dimensions of a tensor [3]. After setting input shapes, this method is used to query the computed output shapes for the given input [3][4]. Inference execution: - context.set_tensor_address(name, ptr): Assigns GPU memory buffers (as integer pointers) to input and output tensors [1][2]. - context.execute_async_v3(stream_handle): Enqueues the inference task to the specified CUDA stream [1][2]. Unlike deprecated V2 methods that used bindings, this V3 method relies on the tensor names previously configured with set_tensor_address [3][5]. Workflow Summary: 1. Create an execution context: context = engine.create_execution_context [1][2]. 2. Set the desired optimization profile (if multiple exist) [3][4]. 3. Set input dimensions: context.set_input_shape(input_name, dimensions) [3][2]. 4. Query/calculate output shape: output_shape = context.get_tensor_shape(output_name) [2][6]. 5. Allocate device memory for inputs and outputs [2][6]. 6. Map memory to tensors: context.set_tensor_address(name, ptr) [1][2]. 7. Execute: context.execute_async_v3(stream_handle) [1][2]. 8. Synchronize the stream to ensure inference completion [1][2][7]. For complex networks or when output shapes are strictly data-dependent, context.infer_shapes() may be used to validate or compute shapes [3][8]. Note that set_tensor_shape is not the standard API for setting input dimensions; set_input_shape is the correct method for defining input runtime dimensions [3][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' "== repository guidance and file headers =="
fd -i 'CODING_GUIDELINES.md' . -t f -x sh -c 'echo "--- $1"; sed -n "1,220p" "$1"' sh {}
printf '%s\n' "== engine build command and input-related options =="
sed -n '1,110p' examples/vla_hidden_states_export/export_hidden_states.py
printf '%s\n' "== inference documentation around invocation =="
sed -n '1,120p' examples/vla_hidden_states_export/README.md
printf '%s\n' "== related engine-input conventions in examples =="
rg -n -C 5 'trtllm-build|remove_input_padding|set_input_shape|set_tensor_address|input_names|position_ids|attention_mask|last_token_ids|past_key_value' examples -g '*.py' -g '*.md' | head -n 500

Repository: NVIDIA/TensorRT-LLM

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

path = Path("examples/vla_hidden_states_export/inference_python.py")
tree = ast.parse(path.read_text())
method = next(
    node for node in ast.walk(tree)
    if isinstance(node, ast.FunctionDef)
    and node.name == "infer_and_extract_hidden_states"
)

calls = []
for node in ast.walk(method):
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
        owner = node.func.value
        if isinstance(owner, ast.Attribute) and owner.attr == "context":
            calls.append((node.lineno, node.func.attr))
print("context calls in AST order:")
for item in sorted(calls):
    print(item)

ordered_names = [name for _, name in sorted(calls)]
print("set_input_shape present:", "set_input_shape" in ordered_names)
print("set_tensor_shape present:", "set_tensor_shape" in ordered_names)
print("first get_tensor_shape index:", next(
    (i for i, name in enumerate(ordered_names) if name == "get_tensor_shape"), None
))
print("first execute_async_v3 index:", next(
    (i for i, name in enumerate(ordered_names) if name == "execute_async_v3"), None
))
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 492


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

path = Path("examples/vla_hidden_states_export/inference_python.py")
tree = ast.parse(path.read_text())
method = next(
    node for node in ast.walk(tree)
    if isinstance(node, ast.FunctionDef)
    and node.name == "infer_and_extract_hidden_states"
)

calls = []
for node in ast.walk(method):
    if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
        owner = node.func.value
        if isinstance(owner, ast.Attribute) and owner.attr == "context":
            calls.append((node.lineno, node.func.attr))

print("context calls in AST order:")
for item in sorted(calls):
    print(item)
PY

Repository: NVIDIA/TensorRT-LLM

Length of output: 366


Set input shapes and bind all required tensors before execution.

Call set_input_shape() for each runtime input before querying output shapes. Remove set_tensor_shape() for full_hidden_states; TensorRT derives output shapes from input shapes. Bind every tensor in self.input_names, and check shape inference and the boolean result of execute_async_v3() before synchronizing.

🤖 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 `@examples/vla_hidden_states_export/inference_python.py` around lines 72 - 98,
Update the inference setup around the execution flow to set runtime shapes for
every tensor in self.input_names before querying any output shapes, and bind
each required input tensor rather than only input_ids. Remove the
set_tensor_shape call for full_hidden_states, validate that TensorRT shape
inference succeeds and that execute_async_v3 returns true, then synchronize only
after successful execution.

Comment on lines +80 to +103
hs_shape = tuple(self.context.get_tensor_shape("full_hidden_states"))
hs_size = int(np.prod([max(1, s) for s in hs_shape]))
hs_dtype = np.float16 # fp16 engine
d_hs = cuda.mem_alloc(hs_size * 2) # fp16 = 2 bytes
self.context.set_tensor_shape("full_hidden_states", hs_shape)
self.context.set_tensor_address("full_hidden_states", int(d_hs))
buffers["full_hidden_states"] = d_hs

# Output: logits (standard)
if "logits" in self.output_names:
logits_shape = tuple(self.context.get_tensor_shape("logits"))
logits_size = int(np.prod([max(1, s) for s in logits_shape]))
d_logits = cuda.mem_alloc(logits_size * 4)
self.context.set_tensor_address("logits", int(d_logits))
buffers["logits"] = d_logits

# --- Execute ---
self.context.execute_async_v3(self.stream.handle)
self.stream.synchronize()

# --- Read full_hidden_states ---
full_hs = np.zeros(hs_size, dtype=np.float16)
cuda.memcpy_dtoh(full_hs, d_hs)
full_hs = full_hs.reshape([max(1, s) for s in hs_shape])

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Allocate outputs from the engine tensor dtype.

export_hidden_states.py accepts fp32, but this code always allocates two bytes per hidden-state element and reads it as np.float16. An fp32 engine can write past d_hs and the host copy uses the wrong dtype. Get each output dtype from engine.get_tensor_dtype() and allocate with its NumPy item size.

🤖 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 `@examples/vla_hidden_states_export/inference_python.py` around lines 80 - 103,
Update the hidden-state and logits buffer handling around full_hidden_states to
derive each tensor’s dtype from engine.get_tensor_dtype(), convert it to the
corresponding NumPy dtype, and allocate using that dtype’s item size. Use the
same dtype when creating host arrays and reshaping copied data, preserving
correct fp16 and fp32 behavior for all outputs.

Comment on lines +129 to +138
parser.add_argument("--waypoint_idx", type=int, default=32000,
help="Token position to extract hidden state from (default: 32000)")
args = parser.parse_args()

engine = HiddenStatesEngine(args.engine_path)

# Dummy input for demonstration
input_ids = np.ones((1, 599), dtype=np.int32) # seq_len=599
full_hs, ego_feature = engine.infer_and_extract_hidden_states(
input_ids, args.waypoint_idx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not use token ID 32000 as a sequence index.

The default waypoint_idx is 32000, but the dummy input has 599 tokens. The default invocation indexes outside full_hs. The test hides this failure with 32000 % 599. Validate the index against the sequence length, or locate the waypoint token during tokenization.

  • examples/vla_hidden_states_export/inference_python.py#L129-L138: reject an out-of-range waypoint index before indexing.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L60-L69: test the out-of-range case instead of reducing it with modulo arithmetic.
📍 Affects 2 files
  • examples/vla_hidden_states_export/inference_python.py#L129-L138 (this comment)
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L60-L69
🤖 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 `@examples/vla_hidden_states_export/inference_python.py` around lines 129 -
138, Validate args.waypoint_idx against the dummy input sequence length before
infer_and_extract_hidden_states indexes full_hs, rejecting values outside the
valid range instead of using the default 32000 with 599 tokens. In
examples/vla_hidden_states_export/tests/test_hidden_states.py lines 60-69,
replace modulo-based indexing with an explicit out-of-range test that verifies
invalid waypoint indices are rejected.

@@ -0,0 +1,34 @@
# Patch: Add full_hidden_states output to TRT-LLM v0.7–v0.21 (all TRT-backend versions)

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the NVIDIA copyright header to each new non-Markdown file.

The repository guidelines require the NVIDIA copyright header with the year of the latest meaningful modification.

  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch#L1-L1: add the header before the patch description.
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L1-L1: add the header before the patch description.
  • examples/vla_hidden_states_export/export_hidden_states.py#L1-L1: add the header before the module docstring.
  • examples/vla_hidden_states_export/inference_python.py#L1-L1: add the header before the module docstring.
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L1-L1: add the header before the module docstring.
📍 Affects 5 files
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch#L1-L1 (this comment)
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L1-L1
  • examples/vla_hidden_states_export/export_hidden_states.py#L1-L1
  • examples/vla_hidden_states_export/inference_python.py#L1-L1
  • examples/vla_hidden_states_export/tests/test_hidden_states.py#L1-L1
🤖 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 `@examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch` at line
1, Add the repository-standard NVIDIA copyright header, using the year of the
latest meaningful modification, at the beginning of each affected file:
examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch:1-1 and
modeling_utils_v1x.patch:1-1 before the patch descriptions;
export_hidden_states.py:1-1, inference_python.py:1-1, and
tests/test_hidden_states.py:1-1 before their module docstrings.

Source: Coding guidelines

Comment thread examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch Outdated
Comment on lines +26 to +32
+ # [VLA] Attach full 3D hidden_states before LogitsProcessor compresses it.
+ # This enables VLA models to access hidden_states at any token position
+ # for downstream task heads (e.g., planning, action prediction).
+ # Usage: SamplingParams(additional_model_outputs=["hidden_states"])
+ # See: https://github.com/NVIDIA/TensorRT-LLM/issues/4414
+ if hasattr(self, '_additional_output_names') and 'hidden_states' in self._additional_output_names:
+ self._additional_outputs = {'hidden_states': output}

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.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline tensorrt_llm/_torch/models/modeling_utils.py \
  --match DecoderModelForCausalLM --view expanded

rg -n -C 4 --glob '*.py' \
  '\b_additional_outputs\b|\b_additional_output_names\b|HandleAdditionalOutputs' \
  tensorrt_llm/_torch

Repository: NVIDIA/TensorRT-LLM

Length of output: 3867


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- modeling_utils.py ---'
sed -n '740,790p' tensorrt_llm/_torch/models/modeling_utils.py

printf '%s\n' '--- handle_additional_outputs.py ---'
wc -l tensorrt_llm/_torch/pyexecutor/handle_additional_outputs.py
sed -n '1,220p' tensorrt_llm/_torch/pyexecutor/handle_additional_outputs.py

printf '%s\n' '--- py_executor call site ---'
sed -n '6535,6595p' tensorrt_llm/_torch/pyexecutor/py_executor.py

printf '%s\n' '--- patch and README ---'
sed -n '1,100p' examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch
sed -n '1,100p' examples/vla_hidden_states_export/README.md

printf '%s\n' '--- related symbols and request/output plumbing ---'
rg -n -C 5 --glob '*.py' \
  'additional_model_outputs|additional_generation_outputs|_additional_outputs|_additional_output_names|batch_outputs' \
  tensorrt_llm/_torch

Repository: NVIDIA/TensorRT-LLM

Length of output: 45503


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- all additional-output symbols ---'
rg -n -C 4 --glob '*.py' \
  'additional_model_outputs|_additional_output_names|_additional_outputs|additional_outputs' .

printf '%s\n' '--- forward-step implementation ---'
rg -n -C 8 'def _forward_step|DecoderModelForCausalLM\(' \
  tensorrt_llm/_torch/pyexecutor tensorrt_llm/_torch/models

printf '%s\n' '--- model output consumers ---'
rg -n -C 6 --glob '*.py' \
  'model\(|model_forward|forward\(' tensorrt_llm/_torch/pyexecutor/py_executor.py \
  | head -n 240

printf '%s\n' '--- additional-output tests and API definitions ---'
rg -n -C 6 --glob '*.py' --glob '*.cpp' --glob '*.h' --glob '*.md' \
  'additional_generation_outputs|additional_context_outputs|additional_model_outputs' \
  . | head -n 320

Repository: NVIDIA/TensorRT-LLM

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- ModelEngine._forward_step ---'
sed -n '6588,6630p' tensorrt_llm/_torch/pyexecutor/model_engine.py

printf '%s\n' '--- PyExecutor._forward_step ---'
sed -n '6424,6505p' tensorrt_llm/_torch/pyexecutor/py_executor.py

printf '%s\n' '--- existing additional-output integration test ---'
sed -n '160,285p' tests/unittest/llmapi/test_additional_model_outputs.py

printf '%s\n' '--- output container shapes and append methods ---'
sed -n '430,575p' tensorrt_llm/_torch/pyexecutor/llm_request.py

printf '%s\n' '--- exact definitions of unpublished attributes ---'
rg -n --glob '*.py' \
  '(^|[^A-Za-z0-9_])_additional_output_names([^A-Za-z0-9_]|$)|(^|[^A-Za-z0-9_])_additional_outputs([^A-Za-z0-9_]|$)' .

Repository: NVIDIA/TensorRT-LLM

Length of output: 17470


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Shape:
    dims: tuple[int, ...]

    `@property`
    def shape0(self):
        return self.dims[0]

    def slice0(self, begin, end):
        return Shape((end - begin, *self.dims[1:]))

    def reshape(self, *dims):
        known = 1
        unknown = None
        for i, dim in enumerate(dims):
            if dim == -1:
                unknown = i
            else:
                known *= dim
        total = 1
        for dim in self.dims:
            total *= dim
        result = list(dims)
        if unknown is not None:
            result[unknown] = total // known
        assert result[unknown] * known == total if unknown is not None else known == total
        return Shape(tuple(result))

    def tile(self, reps):
        assert len(reps) == len(self.dims)
        return Shape(tuple(dim * rep for dim, rep in zip(self.dims, reps)))

# Mirror HandleAdditionalOutputs for two context requests and one generation request.
batch, seq_len, hidden, beam = 2, 5, 4, 1
num_context_tokens = 2 * seq_len
output_length_with_context = num_context_tokens + beam
raw = Shape((batch, seq_len, hidden))

gather_context = raw.shape0 == output_length_with_context
print("raw output:", raw.dims)
print("expected token-major leading length:", output_length_with_context)
print("gather_context:", gather_context)

# Generation path used when gather_context is false.
generation_slice = raw.slice0(0, beam).reshape(1, beam, -1)
print("generation output after handler:", generation_slice.dims)
assert generation_slice.dims == (1, beam, seq_len * hidden)
assert generation_slice.dims != (seq_len, beam, hidden)
PY

printf '%s\n' '--- copyright headers in affected files ---'
for file in examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch \
            examples/vla_hidden_states_export/README.md; do
    printf '%s: ' "$file"
    head -n 5 "$file" | tr '\n' ' ' 
    printf '\n'
done

Repository: NVIDIA/TensorRT-LLM

Length of output: 1057


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass

`@dataclass`
class Shape:
    dims: tuple[int, ...]

    `@property`
    def shape0(self):
        return self.dims[0]

    def slice0(self, begin, end):
        return Shape((end - begin, *self.dims[1:]))

    def reshape(self, *dims):
        total = 1
        for dim in self.dims:
            total *= dim
        known = 1
        unknown = None
        result = list(dims)
        for i, dim in enumerate(result):
            if dim == -1:
                unknown = i
            else:
                known *= dim
        if unknown is not None:
            result[unknown] = total // known
        assert known * (result[unknown] if unknown is not None else 1) == total
        return Shape(tuple(result))

batch, seq_len, hidden, beam = 2, 5, 4, 1
num_context_tokens = 2 * seq_len
expected_length = num_context_tokens + beam
raw = Shape((batch, seq_len, hidden))

gather_context = raw.shape0 == expected_length
print("raw output:", raw.dims)
print("expected token-major leading length:", expected_length)
print("gather_context:", gather_context)

generation_slice = raw.slice0(0, beam).reshape(1, beam, -1)
print("generation output after handler:", generation_slice.dims)
assert generation_slice.dims == (1, beam, seq_len * hidden)
assert generation_slice.dims != (seq_len, beam, hidden)
PY

printf '%s\n' '--- copyright headers in affected files ---'
for file in examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch \
            examples/vla_hidden_states_export/README.md; do
    printf '%s: ' "$file"
    head -n 5 "$file" | tr '\n' ' '
    printf '\n'
done

Repository: NVIDIA/TensorRT-LLM

Length of output: 1057


Define and test one v1.x additional-output contract.

  • DecoderModelForCausalLM.forward() returns a tensor. _additional_output_names is not defined, and _additional_outputs has no consumer. The assignment does not add hidden_states to the output dictionary.
  • HandleAdditionalOutputs expects token-major data. A raw [batch, seq_len, hidden_dim] tensor is misclassified and reshaped to [1, beam_width, seq_len * hidden_dim].
  • Add the bridge, publish the supported shapes ([tokens, hidden_dim] for context and [tokens, beam_width, hidden_dim] for generation), update the README, and add a batched context/generation runtime test.

examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L26-L32
examples/vla_hidden_states_export/README.md#L57-L76

📍 Affects 2 files
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L26-L32 (this comment)
  • examples/vla_hidden_states_export/README.md#L57-L76
🤖 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 `@examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch` around
lines 26 - 32, Define a v1.x additional-output bridge that consumes
DecoderModelForCausalLM.forward() tensors, converts context outputs to [tokens,
hidden_dim] and generation outputs to [tokens, beam_width, hidden_dim], and
publishes them through the output dictionary consumed by HandleAdditionalOutputs
instead of assigning unused _additional_outputs. In
examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch lines 26-32,
implement this bridge for hidden_states and preserve the SamplingParams
additional-output contract; update examples/vla_hidden_states_export/README.md
lines 57-76 to document both supported shapes and add batched context and
generation runtime coverage.

Comment on lines +7 to +10
```
Standard LLM: input → LLM → logits → token sampling → text
VLA model: images+text → LLM → hidden_states → planning head → trajectory
```

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language identifier to this code block.

Use text for the diagram block. This satisfies MD040.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 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 `@examples/vla_hidden_states_export/README.md` around lines 7 - 10, Add the
Markdown language identifier text to the fenced diagram code block in the
README, preserving the existing diagram content unchanged.

Source: Linters/SAST tools

Comment thread examples/vla_hidden_states_export/README.md Outdated
Comment on lines +19 to +25
def test_engine_has_full_hidden_states(engine_path):
"""Verify the engine exports full_hidden_states tensor."""
import tensorrt as trt

logger = trt.Logger(trt.Logger.WARNING)
with open(engine_path, "rb") as f:
engine = trt.Runtime(logger).deserialize_cuda_engine(f.read())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Provide engine_path to pytest.

Pytest resolves engine_path as a fixture. This repository does not define that fixture in the supplied test, so python -m pytest fails during setup. Add a fixture or pytest option that supplies the engine path, or skip these engine-dependent tests when no path is configured.

Also applies to: 41-47

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] 23-23: File path is request-/variable-derived; validate and normalize to prevent path traversal.
Context: open(engine_path, "rb")
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(open-filename-from-request)

🤖 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 `@examples/vla_hidden_states_export/tests/test_hidden_states.py` around lines
19 - 25, Update the engine-dependent tests around
test_engine_has_full_hidden_states to obtain engine_path through a defined
pytest fixture or command-line option, and apply the same change to the related
tests. When no engine path is configured, skip these tests cleanly instead of
allowing fixture setup to fail.

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

Two things need sorting before this can be looked at on the merits.

1. The branch reverts merged work. The single commit here contains, besides the new example, a full revert of #12733 ("Unify sparse attention framework with clean backend interfaces") and its follow-up #17416 — 80+ files across cpp/tensorrt_llm/, tensorrt_llm/_torch/attention_backend/sparse/, tensorrt_llm/_torch/modules/mla.py, and the sparse-attention tests/docs. It re-collapses sparse/dsa/, sparse/rocket/, sparse/skip_softmax/ back into single files, restores sparse_attn_kv_lens / aux_kv_cache_pool_ptr naming in the thop signature, and deletes sparse/hooks.py and registry.py. This is almost certainly a bad rebase/merge rather than intent. Please rebase onto current main so the PR contains only examples/vla_hidden_states_export/.

2. Solution A does not apply to main. The TRT network-build backend is gone: there is no trtllm-build command under tensorrt_llm/commands/, gather_last_token_logits no longer exists anywhere in tensorrt_llm/, and tensorrt_llm/models/modeling_utils.py has no DecoderModelForCausalLM. So export_hidden_states.py, inference_python.py, tests/test_hidden_states.py, and patches/modeling_utils_v0x.patch describe a workflow that cannot be run against the branch they're being added to. An examples/ directory whose entry point is trtllm-build will be a support burden. My suggestion: drop Solution A to a short "historical note (v0.x)" paragraph in the README, and make the example PyTorch-backend-only, with runnable code rather than patch files.

On Solution B, the underlying idea is right and the framework already supports it: HandleAdditionalOutputs (tensorrt_llm/_torch/pyexecutor/py_executor.py:6572) picks up any key named in SamplingParams.additional_model_outputs from the dict a model's forward returns (model_engine.py:6611). So a model that returns {"logits": ..., "hidden_states": ...} already works end-to-end today — no core patch needed. That would make a genuinely useful example: a small modeling_*.py subclass that returns the extra key, plus a llm.generate script reading outputs[0].additional_generation_outputs["hidden_states"]. That's testable in CI and doesn't ask users to patch installed source.

Also: new files need the NVIDIA copyright header (see AGENTS.md), and a new examples/ subdir should be listed in the table in examples/README.md.

+ # for downstream task heads (e.g., planning, action prediction).
+ # Usage: SamplingParams(additional_model_outputs=["hidden_states"])
+ # See: https://github.com/NVIDIA/TensorRT-LLM/issues/4414
+ if hasattr(self, '_additional_output_names') and 'hidden_states' in self._additional_output_names:

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.

This patch is a no-op as written: nothing in the codebase ever sets _additional_output_names, and nothing ever reads self._additional_outputs, so additional_model_outputs=["hidden_states"] would still come back empty.

The framework's actual contract is dict-return from forward. model_engine.py:6611 checks isinstance(outputs, dict), and HandleAdditionalOutputs (py_executor.py:6572) looks up each name in SamplingParams.additional_model_outputs as a key of that dict. So the working version is what your README snippet already shows:

return {
    "logits": self.logits_processor.forward(output, self.lm_head, attn_metadata, return_context_logits),
    "hidden_states": output,
}

Since that path already exists, a model subclass overriding forward this way needs no core patch at all — which would make a much better example than a patch file.


--- a/tensorrt_llm/models/modeling_utils.py
+++ b/tensorrt_llm/models/modeling_utils.py
@@ -XXX,7 +XXX,11 @@ class DecoderModelForCausalLM(DecoderModel):

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.

@@ -XXX,7 +XXX,11 @@ isn't a valid hunk header — git apply and patch will both reject this file. If a patch file is kept at all, generate it with git diff against a real checkout of the target version so the line numbers are concrete.

```python
# tensorrt_llm/_torch/models/modeling_utils.py — DecoderModelForCausalLM.forward()
def forward(self, ..., additional_model_outputs=None):
output = self.model(...) # Full 3D [batch, seq_len, hidden_dim]

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.

# Full 3D [batch, seq_len, hidden_dim] is wrong for the PyTorch backend. With remove-input-padding (always on in the PyTorch path) the model returns packed tokens, shape [num_tokens, hidden_dim], where num_tokens is the flattened sum over the batch. HandleAdditionalOutputs then slices per request using the context/generation token counts, so what a caller gets back is [seq_len, hidden_dim] for that request — no batch dimension. Please correct the shapes throughout the README; the "complete 3D tensor" framing in the intro sets up the wrong expectation.

hs_size = int(np.prod([max(1, s) for s in hs_shape]))
hs_dtype = np.float16 # fp16 engine
d_hs = cuda.mem_alloc(hs_size * 2) # fp16 = 2 bytes
self.context.set_tensor_shape("full_hidden_states", hs_shape)

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.

Three problems make this unrunnable against a real engine:

  1. set_tensor_shape is only valid for input tensors; calling it on full_hidden_states (an output) is an error.
  2. get_tensor_shape is queried before any input shape is bound, so dynamic dims come back as -1. max(1, s) then turns those into 1, so hs_size is far smaller than the real output and cuda.mem_alloc(hs_size * 2) under-allocates — the engine writes past the buffer.
  3. Only input_ids is bound; a TRT-LLM engine needs position ids, sequence lengths, KV-cache pointers etc., so execute_async_v3 will fail on unbound tensors.

Also pycuda is not a TensorRT-LLM dependency. Rather than repairing this, the PyTorch-backend llm.generate + additional_generation_outputs path gives a working ~20-line example with no manual buffer management.

import numpy as np


def test_engine_has_full_hidden_states(engine_path):

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.

The module docstring documents python -m pytest tests/test_hidden_states.py -v, but test_engine_has_full_hidden_states(engine_path) and test_hidden_states_is_3d(engine_path) take a parameter that is not a fixture — pytest will error both with fixture 'engine_path' not found. Either add a --engine-path option plus an engine_path fixture in a conftest.py, or rename these to non-test_ helpers and drop the pytest instruction.

ego_feature = full_hs[0, waypoint_idx, :]

assert ego_feature.shape == (4096,), f"Expected (4096,), got {ego_feature.shape}"
assert np.allclose(ego_feature, full_hs[0, waypoint_idx, :]), "Extraction mismatch"

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.

assert np.allclose(ego_feature, full_hs[0, waypoint_idx, :]) compares the slice to itself — it can never fail, so this test verifies nothing beyond the shape assert on the line above. Relatedly, waypoint_idx = 32000 % 599 on line 62 is arbitrary; if the intent is "a caller-supplied token index", just pick one and say so.

def build_engine_v0x(model_dir, output_dir, dtype="fp16"):
"""Build engine for v0.x TRT backend (requires mark_output patch)."""
cmd = [
"trtllm-build",

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.

trtllm-build no longer exists on maintensorrt_llm/commands/ has only bench, eval, and serve. This script can't run against the branch it's being added to. Please either drop it or move the whole v0.x flow into a clearly-labelled historical note in the README.

@@ -0,0 +1,83 @@
"""

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.

New files need the NVIDIA copyright header (AGENTS.md, "CRITICAL" section). Applies to inference_python.py and tests/test_hidden_states.py too.

@hous-ailab
hous-ailab force-pushed the add-vla-hidden-states-example branch from 30f7336 to 329ffd2 Compare August 8, 2026 12:41

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

🤖 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 `@examples/vla_hidden_states_export/export_hidden_states.py`:
- Line 1: Update the SPDX copyright header at the top of export_hidden_states.py
so the NVIDIA copyright year range ends in 2026 instead of 2025.
- Around line 6-9: Update the version/CLI note in the script to pin the legacy
TRT backend to TRT-LLM v0.13.0, keep trtllm-build scoped to that legacy path,
and describe v1.x as loading Hugging Face checkpoints directly without an
engine-build step. Use the correct v1.x commands trtllm-serve for serving and
trtllm-bench for benchmarking, while preserving the reference to the README’s
Solution B.

In `@examples/vla_hidden_states_export/inference_python.py`:
- Around line 1-2: Update the NVIDIA copyright header’s latest year to 2026 in
examples/vla_hidden_states_export/inference_python.py lines 1-2 and
examples/vla_hidden_states_export/tests/test_hidden_states.py lines 1-2; no
other changes are needed.
- Line 118: Both main functions lack explicit return annotations. Update
examples/vla_hidden_states_export/inference_python.py lines 118-118 and
examples/vla_hidden_states_export/tests/test_hidden_states.py lines 68-68 so
each main signature declares a None return type.
- Around line 40-43: Replace the assertion checking "full_hidden_states" in
self.output_names with an explicit ValueError when the output is missing,
preserving the existing diagnostic message and preventing execution from
continuing without the required engine output.

In `@examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch`:
- Around line 6-22: Align full_hidden_states export with remove_input_padding:
update examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch at
lines 6-22 to either disable packed input for the 3D workflow or explicitly
support/document the packed 2D shape; update
examples/vla_hidden_states_export/export_hidden_states.py at lines 11-14 to
remove the unconditional [batch, seq_len, hidden_dim] claim and document the
required build setting. Preserve the mark_output call in
DecoderModelForCausalLM.forward().
- Around line 1-22: Replace the commit-message prose in
examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch:1-22 with a
valid version-specific unified diff targeting DecoderModelForCausalLM.forward(),
adding mark_output for full_hidden_states immediately before
gather_last_token_logits. Also replace the prose in
examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch:6-24 with an
applicable unified diff implementing the required dictionary-return behavior,
including test context that verifies requested additional outputs and generation
output shapes.
🪄 Autofix

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: dfbebe57-3dec-4002-bf52-94c61a371492

📥 Commits

Reviewing files that changed from the base of the PR and between ecf4f76 and 329ffd2.

📒 Files selected for processing (6)
  • examples/vla_hidden_states_export/README.md
  • examples/vla_hidden_states_export/export_hidden_states.py
  • examples/vla_hidden_states_export/inference_python.py
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch
  • examples/vla_hidden_states_export/tests/test_hidden_states.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/vla_hidden_states_export/README.md

@@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the copyright range to include 2026.

Line 1 ends at 2025, but the patch metadata is dated August 7, 2026. Update the ending year to 2026.

As per coding guidelines, source files must contain the NVIDIA copyright header with the year of the latest meaningful modification.

Proposed fix
-# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
📝 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
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
🤖 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 `@examples/vla_hidden_states_export/export_hidden_states.py` at line 1, Update
the SPDX copyright header at the top of export_hidden_states.py so the NVIDIA
copyright year range ends in 2026 instead of 2025.

Source: Coding guidelines

Comment on lines +6 to +9
NOTE: This script applies to the legacy TRT backend (v0.7-v0.21) which uses
`trtllm-build`. On v1.x (main branch), the build command has changed to
`trtllm serve` / `trtllm bench`. For v1.x, refer to Solution B in the README
(modify model forward to return hidden_states dict).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
cat -n examples/vla_hidden_states_export/export_hidden_states.py | sed -n '1,30p'
printf '%s\n' '--- repository references ---'
rg -n --glob '!node_modules' --glob '!build' --glob '!dist' 'trtllm-build|trtllm-serve|trtllm-bench|trtllm serve|trtllm bench|v0\.7|v0\.21|v1\.x' .
printf '%s\n' '--- referenced upstream README ---'
curl -L --fail --silent --show-error --max-time 20 \
  'https://raw.githubusercontent.com/NVIDIA/TensorRT-LLM/main/examples/models/core/qwen/README.md' |
  rg -n -C 3 'trtllm-build|trtllm-serve|trtllm-bench|trtllm serve|trtllm bench|version|release' || true

Repository: NVIDIA/TensorRT-LLM

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- example directory ---'
git ls-files examples/vla_hidden_states_export
printf '%s\n' '--- example README and related references ---'
if test -f examples/vla_hidden_states_export/README.md; then
  cat -n examples/vla_hidden_states_export/README.md | sed -n '1,240p'
fi
rg -n -C 4 'Solution B|hidden_states|v0\.7|v0\.21|v1\.x|trtllm-build|trtllm-serve|trtllm-bench' \
  examples/vla_hidden_states_export README.md docs examples/models/core/qwen/README.md
printf '%s\n' '--- focused local Qwen README ---'
cat -n examples/models/core/qwen/README.md | sed -n '1,190p'
printf '%s\n' '--- upstream Qwen README focused extract ---'
curl -L --fail --silent --show-error --max-time 20 \
  'https://raw.githubusercontent.com/NVIDIA/TensorRT-LLM/main/examples/models/core/qwen/README.md' |
  sed -n '1,190p'
printf '%s\n' '--- package/version and release references ---'
rg -n --glob '!.git/**' '__version__|version\s*=|v0\.[0-9]+|v1\.[0-9]+' setup.py pyproject.toml tensorrt_llm 2>/dev/null |
  head -200 || true
printf '%s\n' '--- recent tags/releases visible locally ---'
git tag --sort=-version:refname | head -30

Repository: NVIDIA/TensorRT-LLM

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- tracked files in example ---'
git ls-files examples/vla_hidden_states_export
printf '%s\n' '--- example README ---'
if test -f examples/vla_hidden_states_export/README.md; then
  cat -n examples/vla_hidden_states_export/README.md | sed -n '1,220p'
else
  printf '%s\n' 'README.md not present'
fi
printf '%s\n' '--- exact local Qwen command references ---'
rg -n -C 5 'trtllm-build|trtllm-serve|trtllm-bench|Solution B|hidden_states' \
  examples/models/core/qwen/README.md examples/vla_hidden_states_export/export_hidden_states.py
printf '%s\n' '--- exact legacy benchmark build documentation ---'
rg -n -C 4 'trtllm-bench.*build|trtllm-build' \
  docs/source/legacy/performance/perf-benchmarking.md \
  docs/source/legacy/performance/performance-tuning-guide/benchmarking-default-performance.md |
  head -120
printf '%s\n' '--- release/version metadata ---'
git tag --sort=-version:refname | head -40
printf '%s\n' '--- repository version declarations ---'
rg -n --glob 'setup.py' --glob 'pyproject.toml' --glob 'version.py' \
  'version\s*=|__version__' . | head -80 || true
printf '%s\n' '--- upstream Qwen README command lines ---'
curl -L --fail --silent --show-error --max-time 20 \
  'https://raw.githubusercontent.com/NVIDIA/TensorRT-LLM/main/examples/models/core/qwen/README.md' |
  rg -n -C 5 'trtllm-build|trtllm-serve|trtllm-bench|Solution B|hidden_states'

Repository: NVIDIA/TensorRT-LLM

Length of output: 39018


Correct the v1.x CLI description and pin the legacy release.

For v1.x, the PyTorch backend loads Hugging Face checkpoints directly and has no engine-build step. Use trtllm-serve for serving and trtllm-bench for benchmarking. Keep trtllm-build scoped to the legacy TRT backend and pin this script to TRT-LLM v0.13.0, the version tested in the README.

🤖 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 `@examples/vla_hidden_states_export/export_hidden_states.py` around lines 6 -
9, Update the version/CLI note in the script to pin the legacy TRT backend to
TRT-LLM v0.13.0, keep trtllm-build scoped to that legacy path, and describe v1.x
as loading Hugging Face checkpoints directly without an engine-build step. Use
the correct v1.x commands trtllm-serve for serving and trtllm-bench for
benchmarking, while preserving the reference to the README’s Solution B.

Comment thread examples/vla_hidden_states_export/inference_python.py Outdated
Comment thread examples/vla_hidden_states_export/inference_python.py Outdated
Comment thread examples/vla_hidden_states_export/inference_python.py Outdated
Comment on lines +1 to +22
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Hou Song <hous.ailab@gmail.com>
Date: Thu, 7 Aug 2026 22:00:00 +0800
Subject: [PATCH] Export full 3D hidden_states via mark_output

Insert mark_output('full_hidden_states') before gather_last_token_logits
in DecoderModelForCausalLM.forward(), so that the complete [batch, seq_len,
hidden_dim] tensor is exported from the engine.

This addresses the need of VLA models (Orion, OpenVLA) whose downstream
planning heads require hidden_states at a specific token position, not
just the last token's logits.

Related issue: https://github.com/NVIDIA/TensorRT-LLM/issues/4414

Apply to: tensorrt_llm/models/modeling_utils.py
Compatible with: v0.7.0 through v0.21.0 (verified)

NOTE: Line numbers vary by version. Search for `gather_last_token_logits`
in DecoderModelForCausalLM.forward() and add the mark_output line above it:

hidden_states.mark_output('full_hidden_states', self.config.dtype)

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.

⚠️ Potential issue | 🔴 Critical

Provide real patch hunks for both backend patches.

Both files contain commit-message prose, not source diffs. Applying them cannot implement the described backend changes. The v1.x executor preserves extra dictionary keys, and HandleAdditionalOutputs reads requested names from that dictionary, so the v1.x contract requires actual code and runtime coverage. (raw.githubusercontent.com)

  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch#L1-L22: add a concrete version-specific diff containing the mark_output change.
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L6-L24: add the real dictionary-return implementation and test context and generation output shapes.
📍 Affects 2 files
  • examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch#L1-L22 (this comment)
  • examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch#L6-L24
🤖 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 `@examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch` around
lines 1 - 22, Replace the commit-message prose in
examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch:1-22 with a
valid version-specific unified diff targeting DecoderModelForCausalLM.forward(),
adding mark_output for full_hidden_states immediately before
gather_last_token_logits. Also replace the prose in
examples/vla_hidden_states_export/patches/modeling_utils_v1x.patch:6-24 with an
applicable unified diff implementing the required dictionary-return behavior,
including test context that verifies requested additional outputs and generation
output shapes.

Comment thread examples/vla_hidden_states_export/patches/modeling_utils_v0x.patch Outdated
@hous-ailab
hous-ailab force-pushed the add-vla-hidden-states-example branch from 329ffd2 to 94421d4 Compare August 8, 2026 12:52
@hous-ailab
hous-ailab force-pushed the add-vla-hidden-states-example branch 2 times, most recently from 329ffd2 to 7ecfc46 Compare August 8, 2026 12:53
Fixes NVIDIA#4414

Signed-off-by: Hou Song <hous.ailab@gmail.com>
@hous-ailab
hous-ailab force-pushed the add-vla-hidden-states-example branch from 7ecfc46 to d5deab9 Compare August 8, 2026 12:55
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.

how to return hidden_states?

2 participants