Skip to content

fix: skip unavailable delegates and free delegates on model destruction - #197

Open
jslok wants to merge 1 commit into
margelo:mainfrom
jslok:fix/delegate-lifecycle
Open

fix: skip unavailable delegates and free delegates on model destruction#197
jslok wants to merge 1 commit into
margelo:mainfrom
jslok:fix/delegate-lifecycle

Conversation

@jslok

@jslok jslok commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

What

Two related delegate-lifecycle bugs in createModel / HybridTfliteModel:

1. Null delegates are registered with the interpreter

Delegate factories can legitimately return nullptr — e.g. TfLiteCoreMlDelegateCreate on devices without a Neural Engine (the default enabled_devices is ANE-only). createModel passed that nullptr straight into TfLiteInterpreterOptionsAddDelegate, corrupting/crashing interpreter creation.

Fix: skip null delegates so the model falls back to CPU. getDelegates() now also reports only the delegates that were actually registered, instead of claiming a delegate is active when its creation returned null.

2. Delegates are never freed

TFLite's C API does not transfer delegate ownership to the interpreter — the caller must keep the delegate alive and delete it itself after the interpreter is destroyed (TFLite docs). ~HybridTfliteModel deleted the interpreter but never the delegates, so every model destruction leaked the delegate's compiled kernels / driver contexts. For the GPU delegate that's a substantial native+driver allocation per model load — very visible in apps that load/unload models across their lifecycle.

Fix: capture a per-delegate deleter (TfLiteGpuDelegateV2Delete / TfLiteNnapiDelegateDelete / TfLiteCoreMlDelegateDelete) at creation time and run them in the destructor after TfLiteInterpreterDelete (delegates must outlive the interpreter), plus on the interpreter-creation failure path.

Testing

Running in production (React Native scanner app, Android GPU delegate + CoreML) as a patch-package fix — model load/release cycles no longer accumulate native memory, verified with heapprofd before/after.

🤖 Generated with Claude Code

Two related delegate-lifecycle fixes in createModel / HybridTfliteModel:

1. Skip null delegates: delegate factories can legitimately return nullptr
   (e.g. TfLiteCoreMlDelegateCreate on devices without a Neural Engine when
   enabled_devices is ANE-only). Registering that nullptr with
   TfLiteInterpreterOptionsAddDelegate crashes/corrupts the interpreter.
   Now a null delegate is skipped so the model falls back to CPU, and
   getDelegates() reports only the delegates that were actually registered.

2. Free delegates: TFLite's C API does not transfer delegate ownership to
   the interpreter - the caller must delete delegates itself after the
   interpreter is destroyed. They were never freed, so every model
   destruction leaked the delegate's compiled kernels / driver contexts
   (GPU: TfLiteGpuDelegateV2Delete, NNAPI: TfLiteNnapiDelegateDelete,
   CoreML: TfLiteCoreMlDelegateDelete). Deleters run after
   TfLiteInterpreterDelete (delegates must outlive the interpreter), and
   also on the interpreter-creation failure path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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