Skip to content

fix: CPU/ONNX face swapper runtime#1717

Open
kuroi17 wants to merge 3 commits intohacksider:mainfrom
kuroi17:fix/swapper-runtime
Open

fix: CPU/ONNX face swapper runtime#1717
kuroi17 wants to merge 3 commits intohacksider:mainfrom
kuroi17:fix/swapper-runtime

Conversation

@kuroi17
Copy link
Copy Markdown

@kuroi17 kuroi17 commented Mar 30, 2026

Description

This PR fixes the face-swapping feature so that photo and live swaps work correctly on CPU, even if only one ONNX model file is present. It also adds minimal logging to make model/face detection failures visible and easier to debug.

Key Changes

  • CPU path now prefers inswapper_128_fp16.onnx if present; otherwise falls back to inswapper_128.onnx.
  • Swap no longer fails silently when the expected model is missing.
  • Added logs for:
    • Failed to load face swapper model
    • No source face detected
    • No target faces detected
    • Helpful message if InsightFace buffalo_l fails to initialize
  • Preserves GPU/CUDA execution, GUI behavior, and threading logic.

Why

Previously, the CPU path could fail silently if the downloaded ONNX file didn’t match the expected filename. This ensures swaps work reliably and failures are visible.

Testing Checklist

  • CPU fallback: Remove inswapper_128_fp16.onnx, leave inswapper_128.onnx, run python run.py, confirm swaps work.
  • GPU path: Ensure inswapper_128_fp16.onnx exists, run with GPU provider, confirm swaps work.
  • Missing models: Remove both swapper files, run → confirm log shows Failed to load face swapper model.
  • No-face cases: Use images without faces → confirm logs show No source/target face detected.

Branch / Commit

  • Branch: fix/swapper-runtime
  • Commit: fe93537

Summary by Sourcery

Improve reliability and visibility of face swapping on CPU and across runtimes by relaxing model file requirements and adding targeted error logging.

Bug Fixes:

  • Allow CPU face swapper to run when either fp16 or standard inswapper ONNX model is available instead of requiring a specific filename.
  • Prevent silent failures when the face swapper model or InsightFace buffalo_l analyser fails to initialize.

Enhancements:

  • Add one-time status logs when no source or target faces are detected during processing to make swap issues more visible.
  • Adjust model download logic to only auto-download the fp16 swapper when no compatible inswapper model is present.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Mar 30, 2026

Reviewer's Guide

Adjusts the face swapper CPU/ONNX model loading to gracefully handle missing/alternate model files while preserving GPU behavior, and adds one-time user-facing logs for model load and face-detection failures plus safer initialization of the InsightFace face analyser.

Sequence diagram for face swapper ONNX model loading with CPU/GPU fallback

sequenceDiagram
    actor User
    participant App as AppRuntime
    participant FaceSwapper as FaceSwapperModule
    participant FS as FileSystem
    participant HF as HuggingFace
    participant IMZ as InsightFaceModelZoo

    User->>App: Start swapping
    App->>FaceSwapper: pre_check()
    FaceSwapper->>FS: _get_existing_swapper_model_paths()
    FS-->>FaceSwapper: List of existing model_paths (may be empty)
    alt No existing models
        FaceSwapper->>HF: conditional_download(inswapper_128_fp16.onnx)
        HF-->>FaceSwapper: Download complete
    end
    FaceSwapper-->>App: pre_check result

    App->>FaceSwapper: pre_start()
    FaceSwapper->>FS: _get_existing_swapper_model_paths()
    FS-->>FaceSwapper: model_paths
    alt model_paths empty
        FaceSwapper->>App: update_status Failed to load face swapper model: no inswapper model file found.
        FaceSwapper-->>App: False
        App-->>User: Swap aborted
    else model_paths not empty
        FaceSwapper-->>App: True
        App->>FaceSwapper: get_face_swapper()
        FaceSwapper->>FaceSwapper: Check FACE_SWAPPER cache
        alt FACE_SWAPPER is None
            FaceSwapper->>FaceSwapper: Build providers_config from execution_providers
            loop For each model_path
                FaceSwapper->>App: update_status Loading face swapper model from: model_path
                FaceSwapper->>IMZ: get_model(model_path, providers_config)
                alt Load success
                    IMZ-->>FaceSwapper: FACE_SWAPPER instance
                    FaceSwapper->>App: update_status Face swapper model loaded successfully.
                else Load failure
                    IMZ-->>FaceSwapper: Exception
                    FaceSwapper->>FaceSwapper: Remember last_error
                    FaceSwapper->>FaceSwapper: FACE_SWAPPER = None
                end
            end
            alt FACE_SWAPPER is None after all attempts
                FaceSwapper->>App: update_status Failed to load face swapper model with last_error
                FaceSwapper-->>App: None
            else FACE_SWAPPER loaded
                FaceSwapper-->>App: FACE_SWAPPER
                App-->>User: Proceed with swapping
            end
        else FACE_SWAPPER already cached
            FaceSwapper-->>App: Cached FACE_SWAPPER
            App-->>User: Proceed with swapping
        end
    end
Loading

Sequence diagram for face detection and one-time no-face logging during frame processing

sequenceDiagram
    actor User
    participant App as AppRuntime
    participant FaceSwapper as FaceSwapperModule
    participant Analyser as FaceAnalyser

    User->>App: Process frame
    App->>FaceSwapper: process_frame_v2(temp_frame)
    FaceSwapper->>FaceSwapper: processed_frame = preprocess_frame(temp_frame)
    FaceSwapper->>Analyser: get_many_faces(processed_frame)
    Analyser-->>FaceSwapper: detected_faces (may be empty)

    alt detected_faces nonempty
        FaceSwapper->>FaceSwapper: _reset_no_target_faces_log()
        alt modules.globals.many_faces is True
            FaceSwapper->>FaceSwapper: source_face = default_source_face()
            FaceSwapper->>FaceSwapper: Build source_target_pairs for many faces
        else many_faces is False
            FaceSwapper->>FaceSwapper: source_face = default_source_face()
            FaceSwapper->>FaceSwapper: target_face = get_one_face(processed_frame, detected_faces)
            FaceSwapper->>FaceSwapper: Append (source_face, target_face) to pairs
        end
    else detected_faces empty
        FaceSwapper->>FaceSwapper: _log_no_target_faces_once()
        FaceSwapper-->>App: Return original frame (no swaps)
        App-->>User: Frame with no swaps
    end

    rect rgb(230,230,255)
        Note over FaceSwapper: During swapping
        loop For each source_target_pair
            FaceSwapper->>FaceSwapper: swap_face(source_face, target_face, frame)
            alt source_face is None or target_face is None
                FaceSwapper->>FaceSwapper: _log_no_source_face_once() or _log_no_target_faces_once()
                FaceSwapper-->>FaceSwapper: Return frame unchanged
            else source_face and target_face present
                FaceSwapper->>FaceSwapper: _reset_no_source_face_log()
                FaceSwapper->>FaceSwapper: _reset_no_target_faces_log()
                FaceSwapper-->>FaceSwapper: Return swapped frame
            end
        end
    end

    FaceSwapper-->>App: final_frame
    App-->>User: Display swapped frame
Loading

Sequence diagram for InsightFace face analyser initialization with error handling

sequenceDiagram
    actor User
    participant App as AppRuntime
    participant AnalyserModule as FaceAnalyserModule
    participant InsightFace as InsightFaceLibrary

    User->>App: Start face processing
    App->>AnalyserModule: get_face_analyser()
    AnalyserModule->>AnalyserModule: Check FACE_ANALYSER cache
    alt FACE_ANALYSER is None
        AnalyserModule->>AnalyserModule: Acquire FACE_ANALYSER_LOCK
        AnalyserModule->>AnalyserModule: Recheck FACE_ANALYSER is None
        alt Still None
            AnalyserModule->>InsightFace: app.FaceAnalysis(name=buffalo_l, providers, allowed_modules)
            alt Initialization success
                InsightFace-->>AnalyserModule: FACE_ANALYSER instance
                AnalyserModule->>InsightFace: FACE_ANALYSER.prepare(ctx_id=0, det_size=(640,640))
                AnalyserModule-->>App: FACE_ANALYSER
                App-->>User: Proceed with detection
            else Initialization failure
                InsightFace-->>AnalyserModule: Exception e
                AnalyserModule->>AnalyserModule: print failure message with e
                AnalyserModule->>AnalyserModule: FACE_ANALYSER = None
                AnalyserModule-->>App: Raise exception
                App-->>User: Show failure to initialize analyser
            end
        else Already initialized inside lock
            AnalyserModule-->>App: Cached FACE_ANALYSER
        end
    else FACE_ANALYSER already cached
        AnalyserModule-->>App: Cached FACE_ANALYSER
    end
Loading

File-Level Changes

Change Details Files
Make face swapper model discovery tolerant of either fp16 or CPU ONNX model and only auto-download when none exist.
  • Introduced helper to resolve existing inswapper model paths, preferring fp16 but accepting CPU model
  • Changed pre_check to only trigger fp16 download when no swapper model file is present
  • Updated pre_start to fail fast with a user-visible status when no inswapper model files are found
  • Refactored get_face_swapper to try all available model paths in order, reporting success or detailed failure instead of assuming a single filename
modules/processors/frame/face_swapper.py
Add one-time logging for missing source/target faces across frame-processing paths.
  • Added module-level flags and helpers to log 'No source/target face detected' only once until reset
  • Updated swap_face to emit and reset no-source/target-face logs based on detection results and embeddings
  • Hooked no-face logging into process_frame, process_frame_v2, process_frames, and process_image when faces are absent
modules/processors/frame/face_swapper.py
Improve robustness and debuggability of InsightFace buffalo_l analyser initialization.
  • Wrapped FaceAnalysis construction and prepare calls in a try/except block
  • On failure, print a clear diagnostic message about buffalo_l availability and the underlying error, reset the global analyser, and re-raise the exception
modules/face_analyser.py

Possibly linked issues

  • #(not specified): PR makes the loader accept both inswapper_128_fp16.onnx and inswapper_128.onnx, fixing the filename mismatch loading failure

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
Copy Markdown
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 NO_SOURCE_FACE_LOGGED/NO_TARGET_FACES_LOGGED globals are mutated from multiple code paths and may be accessed across threads; consider guarding them with a lock or scoping them per-session/request to avoid race conditions or cross-stream leakage of log suppression.
  • In face_analyser.get_face_analyser, the failure path uses a bare print instead of the existing update_status/logging mechanisms used elsewhere in this PR; aligning this to the same status/logging API would keep error reporting consistent and easier to surface to users.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The NO_SOURCE_FACE_LOGGED/NO_TARGET_FACES_LOGGED globals are mutated from multiple code paths and may be accessed across threads; consider guarding them with a lock or scoping them per-session/request to avoid race conditions or cross-stream leakage of log suppression.
- In face_analyser.get_face_analyser, the failure path uses a bare print instead of the existing update_status/logging mechanisms used elsewhere in this PR; aligning this to the same status/logging API would keep error reporting consistent and easier to surface to users.

## Individual Comments

### Comment 1
<location path="modules/face_analyser.py" line_range="35-42" />
<code_context>
+                        allowed_modules=['detection', 'recognition', 'landmark_2d_106']
+                    )
+                    FACE_ANALYSER.prepare(ctx_id=0, det_size=(640, 640))
+                except Exception as e:
+                    print(
+                        "[DLC.FACE-ANALYSER] Failed to initialize buffalo_l face analyser. "
+                        "Ensure InsightFace model 'buffalo_l' is available. "
+                        f"Error: {e}"
+                    )
+                    FACE_ANALYSER = None
+                    raise
     return FACE_ANALYSER

</code_context>
<issue_to_address>
**suggestion:** Using `print` for error reporting in the face analyser is inconsistent with the rest of the module’s status/logging mechanisms.

This error path writes directly to stdout/stderr instead of using the existing logging/`update_status` mechanism (as in the face swapper), which breaks consistency and makes integration with centralized logging or UI status updates harder. Please switch the `print` to the same logging/status approach used elsewhere so initialization failures are reported consistently.

Suggested implementation:

```python
                except Exception as e:
                    # Use the module's logging/status mechanism instead of printing directly
                    try:
                        # Prefer a centralized status/logging mechanism if available
                        update_status = getattr(modules.globals, "update_status", None)
                        if callable(update_status):
                            update_status(
                                "face_analyser_init_failed",
                                (
                                    "[DLC.FACE-ANALYSER] Failed to initialize buffalo_l face analyser. "
                                    "Ensure InsightFace model 'buffalo_l' is available. "
                                    f"Error: {e}"
                                ),
                            )
                        else:
                            # Fallback to a logger if one is exposed in globals
                            logger = getattr(modules.globals, "logger", None)
                            if logger is not None:
                                logger.error(
                                    "[DLC.FACE-ANALYSER] Failed to initialize buffalo_l face analyser. "
                                    "Ensure InsightFace model 'buffalo_l' is available. "
                                    f"Error: {e}"
                                )
                    finally:
                        FACE_ANALYSER = None
                    raise

```

To fully align with the existing logging/status conventions used elsewhere in the project (especially in the face swapper), you should:

1. Replace the generic `update_status` usage with the exact status function and signature already used for error reporting in the face swapper module (for example, if it is something like `modules.globals.update_status(message, level="error")`, adjust the call accordingly).
2. If the project uses a dedicated logger instance (for example, `modules.globals.LOGGER` or a module-level `logger`), update the fallback branch to call that concrete logger instead of using `modules.globals.logger`.
3. If no `update_status` helper exists and only a logger is used in the rest of this module, you can simplify the `except` block to a single `logger.error(...)` call, making sure a `logger = logging.getLogger(__name__)` is defined at module scope and `import logging` is present at the top of the file.
</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.

Comment thread modules/face_analyser.py
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