Skip to content

perf(face-swapper): use double-check locking to reduce lock contention#1672

Open
laurigates wants to merge 1 commit intohacksider:mainfrom
laurigates:pr/perf-double-check-locking
Open

perf(face-swapper): use double-check locking to reduce lock contention#1672
laurigates wants to merge 1 commit intohacksider:mainfrom
laurigates:pr/perf-double-check-locking

Conversation

@laurigates
Copy link
Contributor

@laurigates laurigates commented Feb 22, 2026

Summary

  • Add double-check locking pattern to get_face_swapper() — check FACE_SWAPPER is None before acquiring lock to reduce contention in multi-threaded live mode
  • Add CoreML warmup inference with dummy data after model load to eliminate first-frame latency spike (triggers JIT compilation and compute plan caching)
  • Set RequireStaticShapes=1 for CoreML provider (required for compute plan caching; previously was 0)
  • Use inswapper_128_fp16.onnx unconditionally for all providers (previously fp16 was CUDA-only; fp16 works correctly on all providers and reduces memory usage)

Test plan

  • Verify face swapping works correctly in live mode with CoreML
  • Verify no race conditions in model initialization
  • Check FPS is maintained or improved
  • Verify first-frame latency is reduced (no visible stutter on start)
  • Test with CPU-only provider to confirm fp16 model works

Generated with Claude Code

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 22, 2026

Reviewer's Guide

Introduces a double-checked locking pattern around the global FACE_SWAPPER initialization to reduce lock contention while preserving thread-safe lazy loading, and slightly adjusts CoreML provider config behavior.

Sequence diagram for double-checked locking in get_face_swapper

sequenceDiagram
    actor Thread1
    actor Thread2
    participant FaceSwapperModule
    participant InsightFaceModelZoo
    participant CoreMLSession

    Thread1->>FaceSwapperModule: get_face_swapper()
    Thread2->>FaceSwapperModule: get_face_swapper()

    rect rgb(235, 245, 255)
        FaceSwapperModule-->>Thread1: check FACE_SWAPPER is None
        FaceSwapperModule-->>Thread2: check FACE_SWAPPER is None
    end

    Thread1->>FaceSwapperModule: acquire THREAD_LOCK
    Thread2-->>FaceSwapperModule: wait for THREAD_LOCK

    FaceSwapperModule-->>FaceSwapperModule: second check FACE_SWAPPER is None

    FaceSwapperModule->>InsightFaceModelZoo: get_model(model_path, providers_config)
    InsightFaceModelZoo-->>FaceSwapperModule: FACE_SWAPPER instance

    alt CoreMLExecutionProvider enabled
        FaceSwapperModule->>CoreMLSession: session = FACE_SWAPPER.session
        FaceSwapperModule->>CoreMLSession: session.run(warmup_input)
        CoreMLSession-->>FaceSwapperModule: warmup complete
    else CoreMLExecutionProvider not enabled
        FaceSwapperModule-->>FaceSwapperModule: skip CoreML warmup
    end

    FaceSwapperModule-->>Thread1: release THREAD_LOCK

    Thread2->>FaceSwapperModule: acquire THREAD_LOCK
    FaceSwapperModule-->>FaceSwapperModule: second check FACE_SWAPPER is not None
    FaceSwapperModule-->>Thread2: return FACE_SWAPPER

    FaceSwapperModule-->>Thread1: return FACE_SWAPPER
Loading

Flow diagram for get_face_swapper initialization, CoreML config, and warmup

flowchart TD
    A[Call get_face_swapper] --> B{FACE_SWAPPER is None?}
    B -- No --> Z[Return existing FACE_SWAPPER]

    B -- Yes --> C[Acquire THREAD_LOCK]
    C --> D{FACE_SWAPPER is None after lock?}
    D -- No --> Y[Release THREAD_LOCK]
    Y --> Z

    D -- Yes --> E[Select model_name based on execution_providers]
    E --> F[Build providers_config]
    F --> G[Call insightface.model_zoo.get_model]
    G --> H{Model load successful?}

    H -- No --> I[update_status Error loading face swapper model]
    I --> J[Set FACE_SWAPPER to None]
    J --> K[Release THREAD_LOCK]
    K --> L[Return None]

    H -- Yes --> M[Assign FACE_SWAPPER]
    M --> N[update_status Face swapper model loaded successfully]
    N --> O{Any CoreMLExecutionProvider in providers_config?}

    O -- No --> P[Release THREAD_LOCK]
    P --> Z

    O -- Yes --> Q[Prepare warmup input from session.get_inputs]
    Q --> R[session.run with warmup input]
    R --> S[update_status CoreML warmup inference complete]
    S --> P

    R -->|Exception| T[update_status CoreML warmup skipped non fatal]
    T --> P

    Z[Return FACE_SWAPPER]
Loading

File-Level Changes

Change Details Files
Apply double-checked locking to FACE_SWAPPER model initialization to reduce lock contention.
  • Move the initial FACE_SWAPPER is None check outside the THREAD_LOCK to avoid locking when the model is already initialized.
  • Retain an inner FACE_SWAPPER is None check inside the THREAD_LOCK block to ensure thread-safe lazy initialization across concurrent callers.
  • Preserve the existing model loading logic, provider configuration, and error handling inside the locked section.
modules/processors/frame/face_swapper.py
Tweak CoreMLExecutionProvider configuration for Apple Silicon during face swapper model loading.
  • Set RequireStaticShapes to 1 instead of 0 when configuring CoreMLExecutionProvider.
  • Keep other CoreMLExecutionProvider configuration options and warmup behavior intact.
modules/processors/frame/face_swapper.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • The new double-checked locking block unconditionally sets model_name = "inswapper_128_fp16.onnx" and removes the previous CUDAExecutionProvider check and default inswapper_128.onnx fallback, which changes model selection behavior; if that’s not intentional, restore the conditional model choice inside the locked section.
  • You changed the CoreML provider config from "RequireStaticShapes": 0 to 1; if this wasn’t explicitly required, consider whether this might break dynamic shape inputs on some platforms and, if necessary, gate it behind a capability check or a separate configuration flag.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new double-checked locking block unconditionally sets `model_name = "inswapper_128_fp16.onnx"` and removes the previous `CUDAExecutionProvider` check and default `inswapper_128.onnx` fallback, which changes model selection behavior; if that’s not intentional, restore the conditional model choice inside the locked section.
- You changed the CoreML provider config from `"RequireStaticShapes": 0` to `1`; if this wasn’t explicitly required, consider whether this might break dynamic shape inputs on some platforms and, if necessary, gate it behind a capability check or a separate configuration flag.

## Individual Comments

### Comment 1
<location> `modules/processors/frame/face_swapper.py:89` </location>
<code_context>
+    if FACE_SWAPPER is None:
+        with THREAD_LOCK:
+            if FACE_SWAPPER is None:
                 model_name = "inswapper_128_fp16.onnx"
-            model_path = os.path.join(models_dir, model_name)
-            update_status(f"Loading face swapper model from: {model_path}", NAME)
</code_context>

<issue_to_address>
**issue (bug_risk):** Model selection now always uses the fp16 variant and no longer respects non-CUDA providers.

With this change, `model_name` is now always set to `"inswapper_128_fp16.onnx"` whenever `FACE_SWAPPER` is initialized, regardless of the available execution providers. This can break or degrade behavior on providers without good fp16 support (e.g., CPU, some CoreML configs). Please restore conditional selection based on `modules.globals.execution_providers`, or gate fp16 behind an explicit capability check.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

if FACE_SWAPPER is None:
with THREAD_LOCK:
if FACE_SWAPPER is None:
model_name = "inswapper_128_fp16.onnx"
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): Model selection now always uses the fp16 variant and no longer respects non-CUDA providers.

With this change, model_name is now always set to "inswapper_128_fp16.onnx" whenever FACE_SWAPPER is initialized, regardless of the available execution providers. This can break or degrade behavior on providers without good fp16 support (e.g., CPU, some CoreML configs). Please restore conditional selection based on modules.globals.execution_providers, or gate fp16 behind an explicit capability check.

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.

1 participant