Skip to content

feat: add android xnnpack delegate - #206

Open
patrickkabwe wants to merge 2 commits into
margelo:mainfrom
patrickkabwe:feat/android-xnnpack-delegate
Open

feat: add android xnnpack delegate#206
patrickkabwe wants to merge 2 commits into
margelo:mainfrom
patrickkabwe:feat/android-xnnpack-delegate

Conversation

@patrickkabwe

Copy link
Copy Markdown
Contributor

XNNPACK is Android-only, cannot be combined with android-gpu or nnapi, and uses an auto-detected thread count capped for mobile devices.

Finding that led to the change:

On Samsung SM-G998B / Android 15 with the example Harness benchmark:

  • Standard TFLite CPU: 20.57ms avg
  • XNNPACK: 11.77ms avg
  • About 43% faster / ~1.75x throughput

@patrickkabwe patrickkabwe changed the title Feat/android xnnpack delegate feat: add android xnnpack delegate Sep 10, 2026

@mrousavy mrousavy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This PR has many code changes that seem unrelated to XNNPACK.
Specifically the change from * to shared_ptr<>.

For stuff like this, please always create atomic PRs - split this change out into a single PR, then add the XNNPACK stacked ontop of it if this really was required to add XNNPACK. If it wasn't required, don't stack.

Individual atomic PRs.

Comment thread cpp/HybridTfliteModel.cpp
namespace margelo::nitro::tflite {

HybridTfliteModel::HybridTfliteModel(TfLiteInterpreter* interpreter,
HybridTfliteModel::HybridTfliteModel(std::shared_ptr<TfLiteInterpreter> interpreter,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this*

Comment thread cpp/HybridTfliteModel.cpp
Comment on lines -31 to -38
HybridTfliteModel::~HybridTfliteModel() {
if (_interpreter != nullptr) {
TfLiteInterpreterDelete(_interpreter);
_interpreter = nullptr;
}
// _modelData (shared_ptr<ArrayBuffer>) is automatically freed
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this*

Comment thread cpp/HybridTfliteModel.hpp
Comment on lines -20 to +21
explicit HybridTfliteModel(TfLiteInterpreter* interpreter, std::shared_ptr<ArrayBuffer> modelData,
explicit HybridTfliteModel(std::shared_ptr<TfLiteInterpreter> interpreter,
std::shared_ptr<ArrayBuffer> modelData,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this*

Comment on lines -36 to +87
TfLiteModel* model = TfLiteModelCreate(modelData->data(), modelData->size());
validateDelegateConfiguration(delegates);

const std::unique_ptr<TfLiteModel, decltype(&TfLiteModelDelete)> model(
TfLiteModelCreate(modelData->data(), modelData->size()), TfLiteModelDelete);
if (model == nullptr) {
throw std::runtime_error("Failed to create TFLite model from data!");
}

// Configure interpreter via options
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
const std::unique_ptr<TfLiteInterpreterOptions, decltype(&TfLiteInterpreterOptionsDelete)>
options(TfLiteInterpreterOptionsCreate(), TfLiteInterpreterOptionsDelete);
if (options == nullptr) {
throw std::runtime_error("TFLite: Failed to create interpreter options!");
}

// Add all hardware accelerated delegates (e.g. GPU, NPU, ...)
// if any. The default CPU delegate will always be available.
// Add all requested delegates. The default CPU kernels stay available for
// operators outside delegated partitions.
std::vector<std::shared_ptr<TfLiteDelegate>> delegateOwners;
delegateOwners.reserve(delegates.size());
for (const TensorflowModelDelegate& delegateType : delegates) {
TfLiteDelegate* delegate = getDelegate(delegateType);
TfLiteInterpreterOptionsAddDelegate(options, delegate);
std::shared_ptr<TfLiteDelegate> delegate = getDelegate(delegateType);
TfLiteInterpreterOptionsAddDelegate(options.get(), delegate.get());
delegateOwners.push_back(std::move(delegate));
}

TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);

// Options and model object can be deleted immediately after interpreter creation.
// (per TFLite C API docs — the model_data buffer must still outlive the interpreter,
// which is handled by _modelData shared_ptr in HybridTfliteModel)
TfLiteInterpreterOptionsDelete(options);
TfLiteModelDelete(model);

if (interpreter == nullptr) {
TfLiteInterpreter* rawInterpreter = TfLiteInterpreterCreate(model.get(), options.get());
if (rawInterpreter == nullptr) {
throw std::runtime_error("Failed to create TFLite interpreter!");
}
const std::shared_ptr<TfLiteInterpreter> interpreter(
rawInterpreter,
[modelData, delegateOwners = std::move(delegateOwners)](TfLiteInterpreter* value) {
(void)modelData;
(void)delegateOwners;
TfLiteInterpreterDelete(value);
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this*

@mrousavy

Copy link
Copy Markdown
Member

Ohh, I just saw #205. This is the separate atomic PR.

Then those changes must not be in this PR (#206) - that got duplicated.

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.

2 participants