Files
wehub-resource-sync 8a852e4b4e
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s
chore: import upstream snapshot with attribution
2026-07-13 12:14:16 +08:00

845 lines
30 KiB
C++

/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
// clang-format off
// This #include needs to precede the inclusion of any other TF Lite header
// file that might depend on the non-mutable schema_generated.h, directly,
// e.g. core/api/op_resolver.h, or indirectly, e.g. core/subgraph.h.
// That's because "tensorflow/lite/schema/mutable/schema_generated.h"
// and "tensorflow/lite/schema/schema_generated.h" both use the same
// header guard macro (FLATBUFFERS_GENERATED_SCHEMA_TFLITE_H_), but have
// different contents (the former is a superset of the latter). In particular
// the one in mutable/ is built with the "--gen-mutable" and "--gen-object-api"
// flags to the flatbuffer schema compiler which cause some additional
// (non-virtual) accessor methods and API functions to be declared.
// The code here uses those methods, so we need to make sure that we get
// the mutable variant of this header.
#include "tensorflow/compiler/mlir/lite/schema/mutable/schema_generated.h"
#include "tensorflow/lite/python/optimize/calibration_wrapper.h"
// clang-format on
// NOLINTBEGIN
// Nolint disables warnings about header file ordering caused by
// `mutable/schema_generated.h`.
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
// NOLINTEND
#include "absl/algorithm/container.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "tensorflow/compiler/mlir/lite/offset_buffer.h"
#include "tensorflow/lite/core/c/common.h"
#include "tensorflow/lite/core/interpreter.h"
#include "tensorflow/lite/core/kernels/register.h"
#include "tensorflow/lite/core/model_builder.h"
#include "tensorflow/lite/mutable_op_resolver.h"
#include "tensorflow/lite/python/interpreter_wrapper/numpy.h"
#include "tensorflow/lite/python/interpreter_wrapper/python_error_reporter.h"
#include "tensorflow/lite/python/interpreter_wrapper/python_utils.h"
#include "tensorflow/lite/shared_library.h"
#include "tensorflow/lite/string_util.h"
#include "tensorflow/lite/tools/optimize/calibration/calibration_reader.h"
#include "tensorflow/lite/tools/optimize/calibration/calibrator.h"
#include "tensorflow/lite/tools/optimize/quantization_wrapper_utils.h"
#include "tensorflow/lite/tools/optimize/quantize_model.h"
#define TFLITE_PY_CHECK(x) \
if ((x) != kTfLiteOk) { \
return error_reporter_->exception(); \
}
#define TFLITE_PY_ENSURE_VALID_INTERPRETER() \
if (!interpreter_) { \
PyErr_SetString(PyExc_ValueError, "Interpreter was not initialized."); \
return nullptr; \
}
namespace tflite {
namespace calibration_wrapper {
namespace {
using ::tflite::interpreter_wrapper::PythonErrorReporter;
using ::tflite::ops::builtin::BuiltinOpResolver;
using ::tflite::optimize::AddIntermediateTensorsToFusedOp;
using ::tflite::optimize::QuantizeModelAllOperators;
using ::tflite::optimize::calibration::BuildLoggingInterpreter;
using ::tflite::optimize::calibration::CalibrationReader;
using ::tflite::python::ImportNumpy;
using ::tflite::python_utils::ConvertFromPyString;
using ::tflite::python_utils::ConvertToPyString;
using ::tflite::python_utils::PyDecrefDeleter;
using ::tflite::python_utils::TfLiteTypeFromPyArray;
using ::tflite::python_utils::TfLiteTypeFromPyType;
std::unique_ptr<ModelT> CreateMutableModel(const Model& model) {
auto copied_model = std::make_unique<ModelT>();
model.UnPackTo(copied_model.get(), nullptr);
return copied_model;
}
bool NoOpModel(const FlatBufferModel& model) {
return model->subgraphs()->size() == 1 &&
(!model->subgraphs()->begin()->operators() ||
model->subgraphs()->begin()->operators()->size() == 0);
}
inline TensorType TfLiteTypeToSchemaType(TfLiteType type) {
switch (type) {
case kTfLiteNoType:
return TensorType_FLOAT32; // TODO(b/129336260): No schema type for none.
case kTfLiteFloat32:
return TensorType_FLOAT32;
case kTfLiteFloat16:
return TensorType_FLOAT16;
case kTfLiteBFloat16:
return TensorType_BFLOAT16;
case kTfLiteFloat64:
return TensorType_FLOAT64;
case kTfLiteInt32:
return TensorType_INT32;
case kTfLiteUInt32:
return TensorType_UINT32;
case kTfLiteInt2:
return TensorType_INT2;
case kTfLiteUInt4:
return TensorType_UINT4;
case kTfLiteFloat8E4M3FN:
return TensorType_FLOAT8_E4M3FN;
case kTfLiteFloat8E5M2:
return TensorType_FLOAT8_E5M2;
case kTfLiteInt4:
return TensorType_INT4;
case kTfLiteUInt8:
return TensorType_UINT8;
case kTfLiteInt8:
return TensorType_INT8;
case kTfLiteInt64:
return TensorType_INT64;
case kTfLiteUInt64:
return TensorType_UINT64;
case kTfLiteString:
return TensorType_STRING;
case kTfLiteBool:
return TensorType_BOOL;
case kTfLiteInt16:
return TensorType_INT16;
case kTfLiteUInt16:
return TensorType_UINT16;
case kTfLiteComplex64:
return TensorType_COMPLEX64;
case kTfLiteComplex128:
return TensorType_COMPLEX128;
case kTfLiteResource:
return TensorType_RESOURCE;
case kTfLiteVariant:
return TensorType_VARIANT;
}
// No default to get compiler error when new type is introduced.
}
bool RegisterCustomOpByName(const char* registerer_name,
MutableOpResolver* resolver) {
// Registerer functions take a pointer to a BuiltinOpResolver as an input
// parameter and return void.
// TODO(b/137576229): We should implement this functionality in a more
// principled way.
typedef void (*RegistererFunctionType)(MutableOpResolver*);
// Look for the Registerer function by name.
RegistererFunctionType registerer = reinterpret_cast<RegistererFunctionType>(
SharedLibrary::GetSymbol(registerer_name));
// Fail in an informative way if the function was not found.
if (registerer == nullptr) {
PyErr_Format(PyExc_ValueError,
"Looking up symbol '%s' failed with error '%s'.",
registerer_name, SharedLibrary::GetError());
return false;
}
// Call the registerer with the resolver.
registerer(resolver);
return true;
}
// Returns the dimension from the stored list in the PyObject. If the given
// PyObject is not a list, it will return absl::optional and set the Python
// error message to notify users.
std::optional<std::vector<int>> ConvertInputShapeToVector(
PyObject* input_shapes, size_t index) {
PyObject* shape = PyList_GetItem(input_shapes, index);
if (!shape || !PyList_Check(shape)) {
PyErr_Format(PyExc_ValueError,
"Invalid %ld input shape: expected to be a list.", index);
return std::nullopt;
}
size_t size = PyList_Size(shape);
std::vector<int> dims(size);
for (size_t dim_index = 0; dim_index < size; ++dim_index) {
PyObject* dim = PyList_GetItem(shape, dim_index);
dims[dim_index] = PyLong_AsLong(dim);
}
return dims;
}
// Finds the starting position of the offset buffer within `model_buffer` if the
// `model_buffer` can be split into base buffer and offset buffer. Returns
// `std::nullopt` iff offset buffer is not used or there were no buffers with
// valid offset. Assumes `model_buffer` is valid.
std::optional<int64_t> GetOffsetBufferStartPosition(
const absl::string_view model_buffer) {
const Model& model = *GetModel(model_buffer.data());
if (!FlatBufferModel::CheckBufferOutsideModel(&model)) {
// Means the offset buffer is not used, e.g.
// `_experimental_use_buffer_offset` is not set.
return std::nullopt;
}
const int64_t int64_max = std::numeric_limits<int64_t>::max();
const int64_t min_offset = absl::c_accumulate(
*model.buffers(), /*init=*/int64_max,
/*binary_op=*/[](const int64_t acc, const Buffer* buffer) -> int64_t {
const int64_t buffer_offset = buffer->offset();
return IsValidBufferOffset(buffer_offset) ? std::min(acc, buffer_offset)
: acc;
});
if (min_offset == int64_max) {
// Means there were no buffers with valid offset.
return std::nullopt;
}
return min_offset;
}
// Splits the model buffer into base buffer and offset buffer. Offset buffer may
// exist when `_experimental_use_buffer_offset` is set.
std::pair<absl::string_view, absl::string_view> SplitOffsetBuffer(
const absl::string_view model_buffer) {
const std::optional<int64_t> offset_buffer_pos =
GetOffsetBufferStartPosition(model_buffer);
if (offset_buffer_pos == std::nullopt) {
return {model_buffer, absl::string_view(model_buffer.data(), /*len=*/0)};
}
const absl::string_view base_buffer(model_buffer.data(), *offset_buffer_pos);
const int64_t offset_buffer_length = model_buffer.size() - *offset_buffer_pos;
const absl::string_view offset_buffer(
model_buffer.data() + *offset_buffer_pos, offset_buffer_length);
return {base_buffer, offset_buffer};
}
// Merges `base_buffer` with the `offset_buffer` that contains the actual tensor
// buffer data.
std::string MergeOffsetBuffer(const absl::string_view base_buffer,
const absl::string_view offset_buffer) {
return absl::StrCat(base_buffer, offset_buffer);
}
// Updates buffer offsets in `base_buffer` by `offset_diff`.
std::string UpdateBufferOffsets(const absl::string_view base_buffer,
const int64_t offset_diff) {
std::string result_buffer(base_buffer);
Model* mutable_model = GetMutableModel(result_buffer.data());
for (Buffer* buffer : *mutable_model->mutable_buffers()) {
if (const int64_t offset = buffer->offset(); IsValidBufferOffset(offset)) {
buffer->mutate_offset(offset + offset_diff);
}
}
return result_buffer;
}
} // namespace
PyObject* AddIntermediateTensors(PyObject* data) {
char* buf = nullptr;
Py_ssize_t length;
std::unique_ptr<PythonErrorReporter> error_reporter(new PythonErrorReporter);
ImportNumpy();
if (ConvertFromPyString(data, &buf, &length) == -1) {
return nullptr;
}
std::unique_ptr<FlatBufferModel> model =
FlatBufferModel::BuildFromBuffer(buf, length, error_reporter.get());
if (!model) {
PyErr_Format(PyExc_ValueError, "Invalid model");
return nullptr;
}
const auto [base_buffer, offset_buffer] =
SplitOffsetBuffer(/*model_buffer=*/absl::string_view(buf, length));
flatbuffers::FlatBufferBuilder builder;
auto tflite_model = CreateMutableModel(*model->GetModel());
if (AddIntermediateTensorsToFusedOp(&builder, tflite_model.get()) !=
kTfLiteOk) {
error_reporter->exception();
return nullptr;
}
const int64_t result_base_buffer_size = builder.GetSize();
if (result_base_buffer_size == 0) {
// When AddIntermediateTensorsToFusedOp early returns, return the model as
// it is.
return ConvertToPyString(buf, length);
}
const int64_t offset_diff =
result_base_buffer_size - static_cast<int64_t>(base_buffer.size());
const std::string updated_result_base_buffer = UpdateBufferOffsets(
/*base_buffer=*/absl::string_view(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
builder.GetSize()),
offset_diff);
const std::string result_buffer =
MergeOffsetBuffer(updated_result_base_buffer, offset_buffer);
return ConvertToPyString(result_buffer.data(), result_buffer.size());
}
CalibrationWrapper::CalibrationWrapper(
std::unique_ptr<Interpreter> interpreter,
std::unique_ptr<BuiltinOpResolver> resolver,
std::unique_ptr<PythonErrorReporter> error_reporter,
std::unique_ptr<FlatBufferModel> model,
std::unique_ptr<CalibrationReader> reader,
std::unique_ptr<std::string> model_str)
: interpreter_(std::move(interpreter)),
error_reporter_(std::move(error_reporter)),
resolver_(std::move(resolver)),
model_(std::move(model)),
reader_(std::move(reader)),
model_str_(std::move(model_str)) {}
CalibrationWrapper::~CalibrationWrapper() = default;
PyObject* CalibrationWrapper::Prepare() {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
TFLITE_PY_CHECK(interpreter_->AllocateTensors());
TFLITE_PY_CHECK(interpreter_->ResetVariableTensors());
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::Prepare(std::string signature_key) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
impl::SignatureRunner* runner =
interpreter_->GetSignatureRunner(signature_key.c_str());
if (runner == nullptr) {
PyErr_Format(PyExc_ValueError, "Invalid signature key: %s",
signature_key.c_str());
return nullptr;
}
TFLITE_PY_CHECK(runner->AllocateTensors());
TFLITE_PY_CHECK(interpreter_->ResetVariableTensors());
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::Prepare(PyObject* input_shapes,
std::string signature_key) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
if (!PyList_Check(input_shapes)) {
PyErr_Format(PyExc_ValueError,
"Invalid input shapes: expected shapes to be a list.");
return nullptr;
}
const int subgraph_index =
interpreter_->GetSubgraphIndexFromSignature(signature_key.c_str());
if (subgraph_index == -1) {
PyErr_Format(PyExc_ValueError, "Invalid signature key: %s",
signature_key.c_str());
return nullptr;
}
auto* subgraph = interpreter_->subgraph(subgraph_index);
const size_t inputs_size = PyList_Size(input_shapes);
if (inputs_size != subgraph->inputs().size()) {
PyErr_Format(PyExc_ValueError,
"Invalid input shapes: expected %ld items got %ld items.",
subgraph->inputs().size(), inputs_size);
return nullptr;
}
for (size_t i = 0; i < inputs_size; ++i) {
std::optional<std::vector<int>> dims =
ConvertInputShapeToVector(input_shapes, i);
if (!dims.has_value()) {
return nullptr;
}
int input_tensor_idx = subgraph->inputs()[i];
if (subgraph->ResizeInputTensor(input_tensor_idx, *dims) != kTfLiteOk) {
PyErr_Format(PyExc_ValueError, "Failed to resize %ld input tensor.", i);
return nullptr;
}
}
return Prepare(signature_key);
}
PyObject* CalibrationWrapper::Prepare(PyObject* input_shapes) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
if (!PyList_Check(input_shapes)) {
PyErr_Format(PyExc_ValueError,
"Invalid input shapes: expected shapes to be a list.");
return nullptr;
}
const size_t inputs_size = PyList_Size(input_shapes);
if (inputs_size != interpreter_->inputs().size()) {
PyErr_Format(PyExc_ValueError,
"Invalid input shapes: expected %ld items got %ld items.",
interpreter_->inputs().size(), inputs_size);
return nullptr;
}
for (size_t i = 0; i < inputs_size; ++i) {
std::optional<std::vector<int>> dims =
ConvertInputShapeToVector(input_shapes, i);
if (!dims.has_value()) {
return nullptr;
}
int input_tensor_idx = interpreter_->inputs()[i];
if (interpreter_->ResizeInputTensor(input_tensor_idx, *dims) != kTfLiteOk) {
PyErr_Format(PyExc_ValueError, "Failed to resize %ld input tensor.", i);
return nullptr;
}
}
return Prepare();
}
PyObject* CalibrationWrapper::FeedTensor(PyObject* input_value,
std::string signature_key) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
if (!PyList_Check(input_value)) {
PyErr_Format(PyExc_ValueError,
"Invalid input type: expected input to be a list.");
return nullptr;
}
const int subgraph_index =
interpreter_->GetSubgraphIndexFromSignature(signature_key.c_str());
if (subgraph_index == -1) {
PyErr_Format(PyExc_ValueError, "Invalid signature key: %s",
signature_key.c_str());
return nullptr;
}
const size_t inputs_size = PyList_Size(input_value);
auto* subgraph = interpreter_->subgraph(subgraph_index);
if (inputs_size != subgraph->inputs().size()) {
PyErr_Format(PyExc_ValueError,
"Invalid input size: expected %ld items got %ld items.",
subgraph->inputs().size(), inputs_size);
return nullptr;
}
for (size_t i = 0; i < inputs_size; ++i) {
PyObject* input = PyList_GetItem(input_value, i);
if (!input) {
return nullptr;
}
int input_tensor_idx = subgraph->inputs()[i];
if (!SetTensor(input_tensor_idx, input, signature_key)) {
return nullptr;
}
}
TFLITE_PY_CHECK(subgraph->Invoke());
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::FeedTensor(PyObject* input_value) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
if (!PyList_Check(input_value)) {
PyErr_Format(PyExc_ValueError,
"Invalid input type: expected input to be a list.");
return nullptr;
}
const size_t inputs_size = PyList_Size(input_value);
if (inputs_size != interpreter_->inputs().size()) {
PyErr_Format(PyExc_ValueError,
"Invalid input size: expected %ld items got %ld items.",
interpreter_->inputs().size(), inputs_size);
return nullptr;
}
for (size_t i = 0; i < inputs_size; ++i) {
PyObject* input = PyList_GetItem(input_value, i);
if (!input) {
return nullptr;
}
int input_tensor_idx = interpreter_->inputs()[i];
if (!SetTensor(input_tensor_idx, input)) {
return nullptr;
}
}
TFLITE_PY_CHECK(interpreter_->Invoke());
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::SetTensor(int index, PyObject* value,
std::string signature_key) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
std::unique_ptr<PyObject, PyDecrefDeleter> array_safe(
PyArray_FromAny(value, nullptr, 0, 0, NPY_ARRAY_CARRAY, nullptr));
if (!array_safe) {
PyErr_SetString(PyExc_ValueError,
"Failed to convert value into readable tensor.");
return nullptr;
}
PyArrayObject* array = reinterpret_cast<PyArrayObject*>(array_safe.get());
const int subgraph_index =
interpreter_->GetSubgraphIndexFromSignature(signature_key.c_str());
if (subgraph_index == -1) {
PyErr_Format(PyExc_ValueError, "Invalid signature key: %s",
signature_key.c_str());
return nullptr;
}
auto* subgraph = interpreter_->subgraph(subgraph_index);
const TfLiteTensor* tensor = subgraph->tensor(index);
if (TfLiteTypeFromPyArray(array) != tensor->type) {
PyErr_Format(PyExc_ValueError,
"Cannot set tensor: "
"Got value of type %s "
"but expected type %s for input %d, name: %s ",
TfLiteTypeGetName(TfLiteTypeFromPyArray(array)),
TfLiteTypeGetName(tensor->type), index, tensor->name);
return nullptr;
}
if (PyArray_NDIM(array) != tensor->dims->size) {
PyErr_Format(PyExc_ValueError,
"Cannot set tensor: Dimension count mismatch, expected %d "
"but found %d",
tensor->dims->size, PyArray_NDIM(array));
return nullptr;
}
std::vector<int> dims(PyArray_NDIM(array));
bool has_unknown_dims = false;
for (int j = 0; j < PyArray_NDIM(array); ++j) {
// Ensure the calibration data input shape is the same as the model input
// shape unless the dimension is unknown.
if (tensor->dims_signature != nullptr &&
tensor->dims_signature->size == tensor->dims->size &&
tensor->dims_signature->data[j] == -1) {
has_unknown_dims = true;
} else if (tensor->dims->data[j] != PyArray_SHAPE(array)[j]) {
PyErr_Format(PyExc_ValueError,
"Cannot set tensor: Size mismatch, expected %d for dim "
"%d but found %ld",
tensor->dims->data[j], j, PyArray_SHAPE(array)[j]);
return nullptr;
}
dims[j] = PyArray_SHAPE(array)[j];
}
// Resize the input tensor if there are unknown dimensions.
if (has_unknown_dims) {
// Does strict checking on the `ResizeInputTensor` call.
TFLITE_PY_CHECK(subgraph->ResizeInputTensorStrict(index, dims));
TFLITE_PY_CHECK(subgraph->AllocateTensors());
}
// Re-read the updated tensor after the allocation is done.
tensor = subgraph->tensor(index);
size_t size = PyArray_NBYTES(array);
if (tensor->type == kTfLiteString) {
DynamicBuffer buffer;
buffer.AddString(reinterpret_cast<const char*>(PyArray_BYTES(array)), size);
buffer.WriteToTensor(subgraph->tensor(index), /*new_shape=*/nullptr);
Py_RETURN_NONE;
}
if (size != tensor->bytes) {
PyErr_Format(PyExc_ValueError,
"numpy array had %zu bytes but expected %zu bytes.", size,
tensor->bytes);
return nullptr;
}
memcpy(tensor->data.raw, PyArray_DATA(array), size);
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::SetTensor(int index, PyObject* value) {
TFLITE_PY_ENSURE_VALID_INTERPRETER();
std::unique_ptr<PyObject, PyDecrefDeleter> array_safe(
PyArray_FromAny(value, nullptr, 0, 0, NPY_ARRAY_CARRAY, nullptr));
if (!array_safe) {
PyErr_SetString(PyExc_ValueError,
"Failed to convert value into readable tensor.");
return nullptr;
}
PyArrayObject* array = reinterpret_cast<PyArrayObject*>(array_safe.get());
const TfLiteTensor* tensor = interpreter_->tensor(index);
if (TfLiteTypeFromPyArray(array) != tensor->type) {
PyErr_Format(PyExc_ValueError,
"Cannot set tensor: "
"Got value of type %s "
"but expected type %s for input %d, name: %s ",
TfLiteTypeGetName(TfLiteTypeFromPyArray(array)),
TfLiteTypeGetName(tensor->type), index, tensor->name);
return nullptr;
}
if (PyArray_NDIM(array) != tensor->dims->size) {
PyErr_Format(
PyExc_ValueError,
"Cannot set tensor: Dimension count mismatch, expected %d but found %d",
tensor->dims->size, PyArray_NDIM(array));
return nullptr;
}
std::vector<int> dims(PyArray_NDIM(array));
bool has_unknown_dims = false;
for (int j = 0; j < PyArray_NDIM(array); ++j) {
// Ensure the calibration data input shape is the same as the model input
// shape unless the dimension is unknown.
if (tensor->dims_signature != nullptr &&
tensor->dims_signature->size == tensor->dims->size &&
tensor->dims_signature->data[j] == -1) {
has_unknown_dims = true;
} else if (tensor->dims->data[j] != PyArray_SHAPE(array)[j]) {
PyErr_Format(PyExc_ValueError,
"Cannot set tensor: Size mismatch, expected %d for dim "
"%d but found %ld",
tensor->dims->data[j], j, PyArray_SHAPE(array)[j]);
return nullptr;
}
dims[j] = PyArray_SHAPE(array)[j];
}
// Resize the input tensor if there are unknown dimensions.
if (has_unknown_dims) {
// Does strict checking on the `ResizeInputTensor` call.
TFLITE_PY_CHECK(interpreter_->ResizeInputTensorStrict(index, dims));
TFLITE_PY_CHECK(interpreter_->AllocateTensors());
}
// Re-read the updated tensor after the allocation is done.
tensor = interpreter_->tensor(index);
size_t size = PyArray_NBYTES(array);
if (tensor->type == kTfLiteString) {
DynamicBuffer buffer;
buffer.AddString(reinterpret_cast<const char*>(PyArray_BYTES(array)), size);
buffer.WriteToTensor(interpreter_->tensor(index), /*new_shape=*/nullptr);
Py_RETURN_NONE;
}
if (size != tensor->bytes) {
PyErr_Format(PyExc_ValueError,
"numpy array had %zu bytes but expected %zu bytes.", size,
tensor->bytes);
return nullptr;
}
memcpy(tensor->data.raw, PyArray_DATA(array), size);
Py_RETURN_NONE;
}
PyObject* CalibrationWrapper::Calibrate() {
const auto [base_buffer, offset_buffer] =
SplitOffsetBuffer(/*model_buffer=*/absl::string_view(
reinterpret_cast<const char*>(model_->allocation()->base()),
model_->allocation()->bytes()));
auto tflite_model = CreateMutableModel(*model_->GetModel());
reader_->AddCalibrationToModel(tflite_model.get(), /*update=*/false);
flatbuffers::FlatBufferBuilder builder;
auto loc = Model::Pack(builder, tflite_model.get());
FinishModelBuffer(builder, loc);
const int64_t result_base_buffer_size = builder.GetSize();
const int64_t offset_diff =
result_base_buffer_size - static_cast<int64_t>(base_buffer.size());
const std::string updated_result_base_buffer = UpdateBufferOffsets(
/*base_buffer=*/absl::string_view(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
result_base_buffer_size),
offset_diff);
const std::string result_buffer =
MergeOffsetBuffer(updated_result_base_buffer, offset_buffer);
return ConvertToPyString(result_buffer.data(), result_buffer.size());
}
PyObject* CalibrationWrapper::QuantizeModel(int input_py_type,
int output_py_type,
bool allow_float,
int activations_py_type,
int bias_py_type) {
return QuantizeModel(
input_py_type, output_py_type, allow_float, activations_py_type,
bias_py_type,
/*disable_per_channel=*/false,
/*disable_per_channel_quantization_for_dense_layers=*/false);
}
PyObject* CalibrationWrapper::QuantizeModel(
int input_py_type, int output_py_type, bool allow_float,
int activations_py_type, int bias_py_type, bool disable_per_channel,
bool disable_per_channel_quantization_for_dense_layers) {
if (NoOpModel(*model_)) {
return ConvertToPyString(model_str_->data(), model_str_->size());
}
TfLiteType input_type = TfLiteTypeFromPyType(input_py_type);
TfLiteType output_type = TfLiteTypeFromPyType(output_py_type);
TfLiteType activations_type = TfLiteTypeFromPyType(activations_py_type);
TfLiteType bias_type = TfLiteTypeFromPyType(bias_py_type);
if (input_type == kTfLiteNoType || output_type == kTfLiteNoType) {
PyErr_SetString(PyExc_ValueError,
"Input/output type cannot be kTfLiteNoType");
return nullptr;
}
auto tflite_model = CreateMutableModel(*model_->GetModel());
reader_->AddCalibrationToModel(tflite_model.get(), /*update=*/false);
flatbuffers::FlatBufferBuilder builder;
auto status = kTfLiteOk;
status = QuantizeModelAllOperators(
&builder, tflite_model.get(), TfLiteTypeToSchemaType(input_type),
TfLiteTypeToSchemaType(output_type), allow_float,
TfLiteTypeToSchemaType(activations_type),
TfLiteTypeToSchemaType(bias_type), disable_per_channel,
disable_per_channel_quantization_for_dense_layers, error_reporter_.get());
if (status != kTfLiteOk) {
error_reporter_->exception();
return nullptr;
}
return ConvertToPyString(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
builder.GetSize());
}
PyObject* CalibrationWrapper::QuantizeModel(int input_py_type,
int output_py_type,
bool allow_float,
const char* operator_output_name) {
string op_name = std::string(operator_output_name);
TfLiteType input_type = TfLiteTypeFromPyType(input_py_type);
TfLiteType output_type = TfLiteTypeFromPyType(output_py_type);
if (input_type == kTfLiteNoType || output_type == kTfLiteNoType) {
PyErr_SetString(PyExc_ValueError,
"Input/output type cannot be kTfLiteNoType");
return nullptr;
}
auto tflite_model = CreateMutableModel(*model_->GetModel());
reader_->AddCalibrationToModel(tflite_model.get(), /*update=*/false);
flatbuffers::FlatBufferBuilder builder;
auto status = optimize::QuantizeModel(
&builder, tflite_model.get(), TfLiteTypeToSchemaType(input_type),
TfLiteTypeToSchemaType(output_type), allow_float, {op_name},
/*activations_type=*/TensorType_INT8, /*bias_type=*/TensorType_INT32,
error_reporter_.get());
if (status != kTfLiteOk) {
error_reporter_->exception();
return nullptr;
}
return ConvertToPyString(
reinterpret_cast<const char*>(builder.GetCurrentBufferPointer()),
builder.GetSize());
}
/*static*/ CalibrationWrapper* CalibrationWrapper::CreateWrapperCPPFromBuffer(
PyObject* data, const std::vector<std::string>& registerers_by_name,
const std::vector<std::function<void(uintptr_t)>>& registerers_by_func,
std::string* error_msg) {
char* buf = nullptr;
Py_ssize_t length;
std::unique_ptr<PythonErrorReporter> error_reporter(new PythonErrorReporter);
ImportNumpy();
if (ConvertFromPyString(data, &buf, &length) == -1) {
*error_msg = "Failed to convert from python string";
return nullptr;
}
std::unique_ptr<FlatBufferModel> model =
FlatBufferModel::BuildFromBuffer(buf, length, error_reporter.get());
if (!model) {
*error_msg = "Invalid model";
return nullptr;
}
auto resolver = std::make_unique<BuiltinOpResolver>();
for (const auto& registerer : registerers_by_name) {
if (!RegisterCustomOpByName(registerer.c_str(), resolver.get())) {
*error_msg =
absl::StrFormat("Looking up symbol '%s' failed with error '%s'.",
registerer.c_str(), SharedLibrary::GetError());
return nullptr;
}
}
for (const auto& registerer : registerers_by_func) {
registerer(reinterpret_cast<uintptr_t>(resolver.get()));
}
std::unique_ptr<Interpreter> interpreter;
std::unique_ptr<CalibrationReader> reader;
auto status =
BuildLoggingInterpreter(*model, *resolver, &interpreter, &reader);
if (status != kTfLiteOk) {
*error_msg = error_reporter->message();
return nullptr;
}
auto model_str = std::make_unique<std::string>(buf, length);
// If we are not going to use this string during quantization, reset the
// pointer and release the memory.
if (!NoOpModel(*model)) {
model_str.reset();
}
auto wrapper = new CalibrationWrapper(
std::move(interpreter), std::move(resolver), std::move(error_reporter),
std::move(model), std::move(reader), std::move(model_str));
return wrapper;
}
} // namespace calibration_wrapper
} // namespace tflite