Apple Silicon performance: 1.5 → 10+ FPS (zero quality loss)

Fix CoreML execution provider falling back to CPU silently, eliminate
redundant per-frame face detection, and optimize the paste-back blend
to operate on the face bounding box instead of the full frame.

All changes are quality-neutral (pixel-identical output verified) and
benefit non-Mac platforms via the shared detection and paste-back
improvements.

Changes:
- Remove unsupported CoreML options (RequireStaticShapes, MaximumCacheSize)
  that caused ORT 1.24 to silently fall back to CPUExecutionProvider
- Add _fast_paste_back(): bbox-restricted erode/blur/blend, skip dead
  fake_diff code in insightface's inswapper (computed but never used)
- process_frame() accepts optional pre-detected target_face to avoid
  redundant get_one_face() call (~30-40ms saved per frame, all platforms)
- In-memory pipeline detects face once and shares across processors
- Fix get_face_swapper() to fall back to FP16 model when FP32 absent
- Fix pre_start() to accept either model variant (was FP16-only check)
- Make tensorflow import conditional (fixes crash on macOS)
- Add missing tqdm dep, make tensorflow/pygrabber platform-conditional

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Max Buckley
2026-04-09 14:28:07 +02:00
parent 8703d394d6
commit bcdd0ce2dd
4 changed files with 140 additions and 74 deletions
+15 -1
View File
@@ -321,6 +321,8 @@ def _run_pipe_pipeline(
bar_fmt = ('{l_bar}{bar}| {n_fmt}/{total_fmt} '
'[{elapsed}<{remaining}, {rate_fmt}{postfix}]')
from modules.face_analyser import get_one_face
try:
with tqdm(total=total_frames, desc='Processing', unit='frame',
dynamic_ncols=True, bar_format=bar_fmt) as progress:
@@ -339,9 +341,21 @@ def _run_pipe_pipeline(
(height, width, 3)
).copy()
# Detect target face once and share across all processors.
# This eliminates the redundant detection that each
# processor would otherwise do internally.
if not modules.globals.many_faces:
target_face = get_one_face(frame)
else:
target_face = None # many_faces mode detects all internally
# Run frame through every active processor
for fp in frame_processors:
frame = fp.process_frame(source_face, frame)
try:
frame = fp.process_frame(source_face, frame, target_face=target_face)
except TypeError:
# Processor doesn't accept target_face kwarg
frame = fp.process_frame(source_face, frame)
writer.stdin.write(frame.tobytes())
processed_count += 1