From 0a87d6356097d747be1206dd5c8097419bed891f Mon Sep 17 00:00:00 2001 From: Max Buckley Date: Wed, 22 Apr 2026 11:45:59 +0200 Subject: [PATCH] Address PR #1775 review: pipelined-detection race and CUDA-graph monkey-patch - core._run_pipe_pipeline: hand the background detector its own copy of the frame. The frame processors mutate in place via paste-back, which was racing with concurrent face detection on the same buffer. - face_swapper._init_cuda_graph_session: replace the `swapper.session.run` monkey-patch with a `_CudaGraphSessionAdapter` that proxies every attribute to the underlying session and only overrides `.run()`. Guarded so repeat init does not double-wrap. Co-Authored-By: Claude Opus 4.7 (1M context) --- modules/processors/frame/core.py | 5 ++- modules/processors/frame/face_swapper.py | 52 +++++++++++++++++------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/modules/processors/frame/core.py b/modules/processors/frame/core.py index 48b660d..a87412d 100644 --- a/modules/processors/frame/core.py +++ b/modules/processors/frame/core.py @@ -357,8 +357,11 @@ def _run_pipe_pipeline( # Start detecting on THIS frame eagerly — the result # will be used for the next iteration. At video # frame rates the face barely moves between frames. + # Hand the detector its own copy: the frame processors + # below mutate `frame` in place (paste-back), which + # would otherwise race with detection. pending_detect = detect_executor.submit( - get_one_face, frame) + get_one_face, frame.copy()) else: target_face = None diff --git a/modules/processors/frame/face_swapper.py b/modules/processors/frame/face_swapper.py index 971a37a..8d24a64 100644 --- a/modules/processors/frame/face_swapper.py +++ b/modules/processors/frame/face_swapper.py @@ -174,6 +174,36 @@ _cuda_graph_session = { _cuda_graph_lock = threading.Lock() +class _CudaGraphSessionAdapter: + """Drop-in wrapper around an ONNX Runtime session. + + Routes ``.run()`` through CUDA graph replay when a recorded graph is + available, and transparently proxies every other attribute to the + underlying session so insightface's INSwapper sees an unchanged API. + """ + + def __init__(self, underlying): + # Use object.__setattr__ to bypass our own __setattr__. + object.__setattr__(self, "_underlying", underlying) + + def run(self, output_names, input_dict, **kwargs): + if _cuda_graph_session['recorded']: + try: + keys = list(input_dict.keys()) + blob = input_dict[keys[0]] + latent = input_dict[keys[1]] + return [_cuda_graph_swap_inference(blob, latent)] + except Exception: + pass + return self._underlying.run(output_names, input_dict, **kwargs) + + def __getattr__(self, name): + return getattr(self._underlying, name) + + def __setattr__(self, name, value): + setattr(self._underlying, name, value) + + def _init_cuda_graph_session(model_path: str, swapper): """Create a CUDA-graph-enabled ONNX session for the swap model. @@ -209,22 +239,14 @@ def _init_cuda_graph_session(model_path: str, swapper): _cuda_graph_session['ort_latent'] = ort_latent _cuda_graph_session['recorded'] = True - # Monkey-patch the swapper's session.run to use CUDA graph replay - _original_run = swapper.session.run + # Wrap swapper.session in an adapter instead of rebinding + # session.run. insightface's INSwapper.get() reads .run via the + # session attribute, so either works; the adapter survives any + # later attribute reads on the session and keeps the original + # session object untouched. + if not isinstance(swapper.session, _CudaGraphSessionAdapter): + swapper.session = _CudaGraphSessionAdapter(swapper.session) - def _graph_run(output_names, input_dict, **kwargs): - if _cuda_graph_session['recorded']: - try: - # input_dict has 'target' (blob) and 'source' (latent) - keys = list(input_dict.keys()) - blob = input_dict[keys[0]] - latent = input_dict[keys[1]] - return [_cuda_graph_swap_inference(blob, latent)] - except Exception: - pass - return _original_run(output_names, input_dict, **kwargs) - - swapper.session.run = _graph_run import sys print(f"[{NAME}] CUDA graph session initialized (swap model)") sys.stdout.flush()