@@ -48,6 +48,21 @@ using ::executorch::runtime::Span;
4848 } \
4949 } while (false )
5050
51+ namespace {
52+ thread_local cudaStream_t g_user_stream = nullptr ;
53+ thread_local bool g_user_stream_set = false ;
54+ } // namespace
55+
56+ CudaStreamGuard::CudaStreamGuard (cudaStream_t stream) : prev_stream_(g_user_stream), prev_set_(g_user_stream_set) {
57+ g_user_stream = stream;
58+ g_user_stream_set = true ;
59+ }
60+
61+ CudaStreamGuard::~CudaStreamGuard () {
62+ g_user_stream = prev_stream_;
63+ g_user_stream_set = prev_set_;
64+ }
65+
5166void TRTLogger::log (Severity severity, const char * msg) noexcept {
5267 if (severity <= Severity::kERROR ) {
5368 ET_LOG (Error, " TensorRT: %s" , msg);
@@ -58,8 +73,23 @@ void TRTLogger::log(Severity severity, const char* msg) noexcept {
5873
5974EngineHandle::~EngineHandle () {
6075 cudaSetDevice (device_id);
61- if (stream != nullptr ) {
62- cudaStreamSynchronize (stream);
76+ // A fast-path execute() may have returned with its enqueue still in flight on the
77+ // caller's stream, still using exec_ctx and the cached staging buffers. Wait on
78+ // the recorded completion event before destroying the context or freeing the
79+ // buffers. We wait on the event, not the stream, so this stays valid even if the
80+ // caller already destroyed the stream. Non-skip executes synchronized inline, so
81+ // inflight_pending is false there. Fall back to a device sync if no event exists.
82+ if (inflight_event != nullptr ) {
83+ if (inflight_pending) {
84+ cudaError_t err = cudaEventSynchronize (inflight_event);
85+ if (err != cudaSuccess) {
86+ ET_LOG (Error, " EngineHandle::~EngineHandle: cudaEventSynchronize failed: %s" , cudaGetErrorString (err));
87+ cudaGetLastError (); // clear sticky error; tear down regardless
88+ }
89+ inflight_pending = false ;
90+ }
91+ } else {
92+ cudaDeviceSynchronize ();
6393 }
6494 for (void * p : cached_input_ptrs) {
6595 if (p != nullptr ) {
@@ -74,9 +104,9 @@ EngineHandle::~EngineHandle() {
74104 exec_ctx.reset ();
75105 engine.reset ();
76106 runtime.reset ();
77- if (stream != nullptr ) {
78- cudaStreamDestroy (stream );
79- stream = nullptr ;
107+ if (inflight_event != nullptr ) {
108+ cudaEventDestroy (inflight_event );
109+ inflight_event = nullptr ;
80110 }
81111}
82112
@@ -226,9 +256,12 @@ Result<DelegateHandle*> TensorRTBackend::init(
226256 return Error::InvalidProgram;
227257 }
228258
229- cuda_err = cudaStreamCreate (&handle->stream );
259+ // Created while device_id is current so the event belongs to the engine's device.
260+ // It orders a later execute()/teardown after a skip-sync enqueue (see execute()
261+ // and ~EngineHandle). Blocking-sync so the host yields instead of busy-spinning.
262+ cuda_err = cudaEventCreateWithFlags (&handle->inflight_event , cudaEventDisableTiming | cudaEventBlockingSync);
230263 if (cuda_err != cudaSuccess) {
231- ET_LOG (Error, " TensorRTBackend::init: cudaStreamCreate failed: %s" , cudaGetErrorString (cuda_err));
264+ ET_LOG (Error, " TensorRTBackend::init: cudaEventCreateWithFlags failed: %s" , cudaGetErrorString (cuda_err));
232265 return Error::InvalidProgram;
233266 }
234267
@@ -298,22 +331,57 @@ Error TensorRTBackend::execute(BackendExecutionContext& context, DelegateHandle*
298331 return Error::InvalidArgument;
299332 }
300333
301- cudaError_t cuda_err = cudaSetDevice (engine->device_id );
334+ int entry_device = -1 ;
335+ cudaError_t cuda_err = cudaGetDevice (&entry_device);
302336 if (cuda_err != cudaSuccess) {
303- ET_LOG (
304- Error,
305- " TensorRTBackend::execute: cudaSetDevice(%d) failed: %s" ,
306- engine->device_id ,
307- cudaGetErrorString (cuda_err));
337+ ET_LOG (Error, " TensorRTBackend::execute: cudaGetDevice failed: %s" , cudaGetErrorString (cuda_err));
308338 return Error::InvalidProgram;
309339 }
340+ // Put the engine on its own device for multi-GPU correctness, restoring the
341+ // caller's device on exit; green-context confinement rides the selected stream,
342+ // independent of the current device/context.
343+ const bool switch_device = (entry_device != engine->device_id );
344+ if (switch_device) {
345+ cuda_err = cudaSetDevice (engine->device_id );
346+ if (cuda_err != cudaSuccess) {
347+ ET_LOG (
348+ Error,
349+ " TensorRTBackend::execute: cudaSetDevice(%d) failed: %s" ,
350+ engine->device_id ,
351+ cudaGetErrorString (cuda_err));
352+ return Error::InvalidProgram;
353+ }
354+ }
355+ struct DeviceRestore {
356+ int device;
357+ bool active;
358+ ~DeviceRestore () {
359+ if (active) {
360+ cudaSetDevice (device);
361+ }
362+ }
363+ } device_restore{entry_device, switch_device};
310364
311365 std::unique_lock<std::mutex> lock (engine->mu );
312366
313367 nvinfer1::IExecutionContext* ctx = engine->exec_ctx .get ();
314- cudaStream_t stream = engine->stream ;
315368 TORCHTRT_ET_CHECK_NOT_NULL (ctx, Error::InvalidState, " TensorRTBackend::execute: backend is not initialized" );
316- TORCHTRT_ET_CHECK_NOT_NULL (stream, Error::InvalidState, " TensorRTBackend::execute: backend is not initialized" );
369+
370+ // A prior fast-path execute() may have returned with its enqueue still in flight
371+ // on the shared exec_ctx. Wait for it before reconfiguring the context below:
372+ // TensorRT forbids mutating a context while one of its enqueues is in flight, and
373+ // setInputShape/setTensorAddress run on the host, so this must be a host-side wait.
374+ if (engine->inflight_pending ) {
375+ cuda_err = cudaEventSynchronize (engine->inflight_event );
376+ engine->inflight_pending = false ;
377+ if (cuda_err != cudaSuccess) {
378+ ET_LOG (Error, " TensorRTBackend::execute: cudaEventSynchronize failed: %s" , cudaGetErrorString (cuda_err));
379+ return Error::InvalidProgram;
380+ }
381+ }
382+ cudaStream_t stream = g_user_stream_set ? g_user_stream : cudaStreamPerThread;
383+ bool output_staged_to_host = false ;
384+ bool input_staged_from_host = false ;
317385
318386 if (engine->cached_input_ptrs .empty ()) {
319387 engine->cached_input_ptrs .resize (num_inputs, nullptr );
@@ -392,6 +460,7 @@ Error TensorRTBackend::execute(BackendExecutionContext& context, DelegateHandle*
392460 engine->cached_input_sizes [i] = needed;
393461 }
394462 bind_ptr = engine->cached_input_ptrs [i];
463+ input_staged_from_host = true ;
395464 cuda_err = cudaMemcpyAsync (bind_ptr, et_in.const_data_ptr (), needed, cudaMemcpyHostToDevice, stream);
396465 if (cuda_err != cudaSuccess) {
397466 ET_LOG (
@@ -486,6 +555,7 @@ Error TensorRTBackend::execute(BackendExecutionContext& context, DelegateHandle*
486555 engine->cached_output_sizes [o] = needed;
487556 }
488557 bind_ptr = engine->cached_output_ptrs [o];
558+ output_staged_to_host = true ;
489559 outputs_needing_copy.push_back ({o, bind_ptr});
490560 }
491561
@@ -499,28 +569,56 @@ Error TensorRTBackend::execute(BackendExecutionContext& context, DelegateHandle*
499569 // 4. Enqueue inference on the current CUDA stream
500570 // ------------------------------------------------------------------
501571 if (!ctx->enqueueV3 (stream)) {
502- ET_LOG (Error, " TensorRTBackend::execute: enqueueV3 failed" );
572+ ET_LOG (
573+ Error,
574+ " TensorRTBackend::execute: enqueueV3 failed. If a CUDA green context is "
575+ " current, scope a CudaStreamGuard with a green-context stream: "
576+ " cudaStreamPerThread is invalid while a green context is current." );
503577 return Error::InvalidState;
504578 }
505579
506- for (auto & output : outputs_needing_copy) {
507- exec_aten::Tensor et_out = args[num_inputs + output.first ]->toTensor ();
508- cuda_err =
509- cudaMemcpyAsync (et_out.mutable_data_ptr (), output.second , et_out.nbytes (), cudaMemcpyDeviceToHost, stream);
580+ // The engine work is now in flight on `stream`. Decide whether to wait for it:
581+ // must_sync = an output is staged to host (the caller reads the D2H result on
582+ // return), an input was staged from host (its async H2D read the caller's host
583+ // buffer, which the caller may reuse once we return), or no caller stream is
584+ // active (preserve the historical "results ready on return" behavior).
585+ // Otherwise (caller stream + all I/O device-resident) leave the work enqueued so
586+ // it composes with the caller's later GPU work, and record inflight_event so the
587+ // next execute() and the destructor wait before reusing/freeing exec_ctx. The D2H
588+ // copies live in the must_sync branch: an output staged to host always sets
589+ // output_staged_to_host, so outputs_needing_copy is empty on the skip path.
590+ const bool must_sync = output_staged_to_host || input_staged_from_host || !g_user_stream_set;
591+ if (must_sync) {
592+ for (auto & output : outputs_needing_copy) {
593+ exec_aten::Tensor et_out = args[num_inputs + output.first ]->toTensor ();
594+ cuda_err =
595+ cudaMemcpyAsync (et_out.mutable_data_ptr (), output.second , et_out.nbytes (), cudaMemcpyDeviceToHost, stream);
596+ if (cuda_err != cudaSuccess) {
597+ ET_LOG (
598+ Error,
599+ " TensorRTBackend::execute: D2H copy failed for output %zu: %s" ,
600+ output.first ,
601+ cudaGetErrorString (cuda_err));
602+ return Error::InvalidProgram;
603+ }
604+ }
605+ cuda_err = cudaStreamSynchronize (stream);
606+ engine->inflight_pending = false ;
510607 if (cuda_err != cudaSuccess) {
511- ET_LOG (
512- Error,
513- " TensorRTBackend::execute: D2H copy failed for output %zu: %s" ,
514- output.first ,
515- cudaGetErrorString (cuda_err));
608+ ET_LOG (Error, " TensorRTBackend::execute: cudaStreamSynchronize failed: %s" , cudaGetErrorString (cuda_err));
516609 return Error::InvalidProgram;
517610 }
518- }
519-
520- cuda_err = cudaStreamSynchronize (stream);
521- if (cuda_err != cudaSuccess) {
522- ET_LOG (Error, " TensorRTBackend::execute: cudaStreamSynchronize failed: %s" , cudaGetErrorString (cuda_err));
523- return Error::InvalidProgram;
611+ } else {
612+ cuda_err = cudaEventRecord (engine->inflight_event , stream);
613+ if (cuda_err != cudaSuccess) {
614+ // Could not arm the completion marker; drain now so a later execute() or the
615+ // destructor never reconfigures or frees exec_ctx while this enqueue runs.
616+ ET_LOG (Error, " TensorRTBackend::execute: cudaEventRecord failed: %s" , cudaGetErrorString (cuda_err));
617+ (void )cudaStreamSynchronize (stream);
618+ engine->inflight_pending = false ;
619+ return Error::InvalidProgram;
620+ }
621+ engine->inflight_pending = true ;
524622 }
525623 return Error::Ok;
526624}
0 commit comments