Skip to content

fix: accept host-resident shape-tensor inputs without a device round trip - #4718

Open
SrivastavaKshitij wants to merge 2 commits into
pytorch:mainfrom
SrivastavaKshitij:perf/host-shape-tensor-inputs
Open

SrivastavaKshitij wants to merge 2 commits into
pytorch:mainfrom
SrivastavaKshitij:perf/host-shape-tensor-inputs

Conversation

@SrivastavaKshitij

Copy link
Copy Markdown

Description

A TensorRT shape-tensor input is read from host memory, but setup_input_tensors required every
input to be on the device and then copied a shape input back to the host with .cpu(), and
execute_engine moved any host input to the device before that. A shape value the caller already
holds on the host was therefore pushed device-side and pulled straight back, and the .cpu() copy
synchronizes the stream — the host blocks until the work already queued on the stream has finished.

This accepts a host-resident shape-tensor input and uses it as is. A device-resident shape input is
unchanged (still copied to the host); every non-shape input still must be on the device.

Before (a host shape value is moved to the device, then copied back — the slower path):

shape input on device : 157.8 us / call
shape input on host   : 196.8 us / call

After (a host shape value is used directly):

shape input on device : 155.5 us / call
shape input on host   : 114.8 us / call

The copy the host path avoids is a stream synchronization. With a backlog of GPU work queued on the
stream, input.clone().contiguous().cpu().to(int64) blocks the host for the full backlog (~120 ms
in the benchmark) while input.contiguous().to(int64) does not. On a GPU-bound model the wall
clock is unchanged; the win is the removed host stall and the tighter host/device overlap.

Issues

Closes #4717

Type of change

  • Bug fix (non-breaking change which fixes an issue)

Testing

The benchmark in the linked issue (benchmark_host_shape_inputs.py, numbers above), one L4 in
nvcr.io/nvidia/pytorch:26.07-py3, built from this branch. Correctness: the engine's output matches eager for a shape input placed on
the host and on the device, across shape values 8/64/200. No regressions: tests/py/dynamo/runtime/
is 164 passed / 68 skipped, and tests/py/dynamo/conversion/test_arange_aten.py (the shape-tensor
input converter) is 20 passed with 1 failure that also fails on main unchanged.

Checklist:

  • My code follows the style guidelines of this project (You can use the linters)
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas and hacks
  • I have made corresponding changes to the documentation
  • I have added tests to verify my fix or my feature
  • New and existing unit tests pass locally with my changes
  • I have added the relevant labels to my PR in so that relevant reviewers are notified

@meta-cla meta-cla Bot added the cla signed label Sep 15, 2026
@github-actions github-actions Bot added component: core Issues re: The core compiler component: runtime labels Sep 15, 2026
@SrivastavaKshitij

Copy link
Copy Markdown
Author

@apbose @narendasan

// A device tensor is copied back with .cpu(), which synchronizes the stream; a host tensor
// is used as is, which does not, so passing shape inputs on the host avoids a per-call sync.
auto input_cpu = inputs[i].is_cuda() ? inputs[i].clone().contiguous().cpu().to(torch::kInt64)
: inputs[i].contiguous().to(torch::kInt64);

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.

@apbose do you think we need the clone on the second arm?

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.

yeah I dont think the clone is required since we anyways deep copy the shape tensors to the engine owned vectors

Comment thread core/runtime/execute_engine.cpp Outdated
if (!inp.defined() || inp.is_cuda()) {
continue;
}
if (i < compiled_engine->input_binding_infos.size() && compiled_engine->input_binding_infos[i].is_shape_tensor) {

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.

in this, I think we should also check if the shape tensor is on cpu? basically adding && inp.is_cpu I don't think we have cases like that rn, but would be more fool-proof

"calling the TensorRT engine (e.g. tensor.cuda() or tensor.to(device)).");
inp = inp.cuda();
}

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.

can we have a test for the host shape input path? the existing arange have shape inputs on cuda. We could use the ShapeInputModel from #4717 just to verify the correctness

@github-actions github-actions Bot added the component: tests Issues re: Tests label Sep 18, 2026
@SrivastavaKshitij

SrivastavaKshitij commented Sep 18, 2026

Copy link
Copy Markdown
Author

Thanks! All three addressed in 2ec01dff2:

  • Redundant clone() (@narendasan/@apbose): removed — .cpu() already returns a fresh host tensor and the values are deep-copied into active_shape_tensor_values right after.
  • is_cpu() guard on the preamble (@apbose): added — a shape input on any non-CPU, non-CUDA device now falls through to the CUDA move instead of being left in place.
  • Host shape-input test (@apbose): added tests/py/dynamo/runtime/test_host_shape_inputs.py using ShapeInputModel from Host-resident TensorRT shape-tensor inputs incur a device round trip and a per-call stream sync #4717; runs the shape input on the host, asserts it matches eager (8/64/200) and the CUDA-shape-input result.

new tests pass; tests/py/dynamo/runtime/ 166 passed / 68 skipped.

…trip

A TensorRT shape-tensor input is read from host memory, but setup_input_tensors
required every input on the device and copied a shape input back with .cpu(),
and execute_engine moved any host input to the device first. A host shape value
was therefore pushed device-side and pulled straight back, and the .cpu() copy
synchronizes the stream. Accept a host-resident shape-tensor input as is; device
inputs and all non-shape inputs are unchanged.
@apbose
apbose force-pushed the perf/host-shape-tensor-inputs branch from 2ec01df to 4c4613f Compare September 23, 2026 03:48
…input test

- setup_input_tensors: remove clone() on the device arm; .cpu() already returns a
  fresh host tensor and values are deep-copied into active_shape_tensor_values.
- execute_engine preamble: only leave a shape-tensor input in place when it is on
  CPU; a shape tensor on any other non-CUDA device falls through to the CUDA move.
- add tests/py/dynamo/runtime/test_host_shape_inputs.py covering the host shape
  input path (arange converter tests only exercise a CUDA shape input).
@apbose
apbose force-pushed the perf/host-shape-tensor-inputs branch from 4c4613f to b7b611f Compare September 23, 2026 03:51
@apbose

apbose commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

approved. waiting on full CI to run after rebase

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Host-resident TensorRT shape-tensor inputs incur a device round trip and a per-call stream sync

3 participants