Add endian-aware flatbuffer conversions to MicroAllocator.
Currently, MicroAllocator manually maps TfLite*Array struct values directly to flatbuffer values. This change cleans up other instances inside MicroAllocator that are not endian-aware. This works only on little-endian (LE) architecture systems because of the layout of TfLite*Array: struct TfLiteIntArray { int size; int data[]; } The compiler maintains mapping, but |size| and |data| are laid out as the following in LE: [lowest-order-byte(e.g. data) .... highest-order-byte(e.g. size)] Casting and remapping work on LE because the vector is written in the lowest-order-byte sequence. On BE systems, this memory savings trick does not work and requires a malloc from the arena and manual copying of values from the flatbuffer. PiperOrigin-RevId: 317730072 Change-Id: I1baff898356e3d82b2faed6468a50ae44acd3082
This commit is contained in:
parent
28582fb549
commit
a3dc11ea13
@ -392,28 +392,48 @@ TfLiteStatus CommitPlan(ErrorReporter* error_reporter, MemoryPlanner* planner,
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Allocate a TfLiteIntArray and copy the contents of a FlatBuffers Vector
|
||||
// into it.
|
||||
template <class T>
|
||||
TfLiteStatus FlatBufferIntArrayToTfLiteIntArray(
|
||||
// Handles architecture safe mapping of flatbuffer vectors to a TfLite*Array
|
||||
// struct. Matching types are required (e.g. float and TfLiteFloatArray).
|
||||
template <typename kFlatBufferVectorType, typename kTfLiteArrayType>
|
||||
TfLiteStatus FlatBufferVectorToTfLiteTypeArray(
|
||||
SimpleMemoryAllocator* allocator, ErrorReporter* error_reporter,
|
||||
const flatbuffers::Vector<T>* flat_array, TfLiteIntArray** result) {
|
||||
TfLiteIntArray* ret =
|
||||
reinterpret_cast<TfLiteIntArray*>(allocator->AllocateFromTail(
|
||||
TfLiteIntArrayGetSizeInBytes(flat_array->Length()),
|
||||
alignof(TfLiteIntArray)));
|
||||
if (nullptr == ret) {
|
||||
TF_LITE_REPORT_ERROR(
|
||||
error_reporter,
|
||||
"Failed to allocate %d bytes of memory to copy an array.",
|
||||
TfLiteIntArrayGetSizeInBytes(flat_array->Length()));
|
||||
return kTfLiteError;
|
||||
const flatbuffers::Vector<kFlatBufferVectorType>* flatbuffer_array,
|
||||
kTfLiteArrayType** result) {
|
||||
TFLITE_DCHECK(error_reporter != nullptr);
|
||||
TFLITE_DCHECK(flatbuffer_array != nullptr);
|
||||
// Only two conversions are supported - float and int32 - ensure that these
|
||||
// match at compile time instead of duplicating functions here:
|
||||
static_assert((std::is_same<kFlatBufferVectorType, int32_t>() &&
|
||||
std::is_same<kTfLiteArrayType, TfLiteIntArray>()) ||
|
||||
(std::is_same<kFlatBufferVectorType, float>() &&
|
||||
std::is_same<kTfLiteArrayType, TfLiteFloatArray>()));
|
||||
if (FLATBUFFERS_LITTLEENDIAN) {
|
||||
// On little-endian machines, TfLite*Array happens to have the same memory
|
||||
// layout as flatbuffers:Vector<kFlatBufferVectorType>, so we can
|
||||
// reinterpret_cast the flatbuffer vector and avoid a copy and malloc.
|
||||
*result = const_cast<kTfLiteArrayType*>(
|
||||
reinterpret_cast<const kTfLiteArrayType*>(flatbuffer_array));
|
||||
} else {
|
||||
// Big-endian architecture can not use the same memory layout as
|
||||
// flatbuffers::Vector<kFlatBufferVectorType>. Allocate from the tail and
|
||||
// copy values from the flatbuffer into the newly allocated chunk.
|
||||
kTfLiteArrayType* array =
|
||||
reinterpret_cast<kTfLiteArrayType*>(allocator->AllocateFromTail(
|
||||
TfLiteIntArrayGetSizeInBytes(flatbuffer_array->Length()),
|
||||
alignof(kTfLiteArrayType)));
|
||||
if (array == nullptr) {
|
||||
TF_LITE_REPORT_ERROR(
|
||||
error_reporter,
|
||||
"Failed to allocate %d bytes of memory to copy an array.",
|
||||
TfLiteIntArrayGetSizeInBytes(flatbuffer_array->Length()));
|
||||
return kTfLiteError;
|
||||
}
|
||||
array->size = flatbuffer_array->Length();
|
||||
for (int i = 0; i < array->size; ++i) {
|
||||
array->data[i] = flatbuffer_array->Get(i);
|
||||
}
|
||||
*result = array;
|
||||
}
|
||||
ret->size = flat_array->Length();
|
||||
for (int64_t i = 0; i < static_cast<int64_t>(flat_array->Length()); i++) {
|
||||
ret->data[i] = flat_array->Get(i);
|
||||
}
|
||||
*result = ret;
|
||||
return kTfLiteOk;
|
||||
}
|
||||
|
||||
@ -469,27 +489,17 @@ TfLiteStatus InitializeTfLiteTensorFromFlatbuffer(
|
||||
TF_LITE_ENSURE_STATUS(BytesRequiredForTensor(
|
||||
flatbuffer_tensor, &result->bytes, &type_size, error_reporter));
|
||||
|
||||
// TODO(b/159043126): Cleanup endian casting by doing all endian casting in
|
||||
// one spot:
|
||||
if (flatbuffer_tensor.shape() == nullptr) {
|
||||
// flatbuffer_tensor.shape() can return a nullptr in the case of a scalar
|
||||
// tensor.
|
||||
result->dims = const_cast<TfLiteIntArray*>(&kZeroLengthIntArray);
|
||||
} else if (!FLATBUFFERS_LITTLEENDIAN) {
|
||||
// Big-endian architecture. Copy and byte-swap the little-endian shape
|
||||
// data.
|
||||
TF_LITE_ENSURE_STATUS(FlatBufferIntArrayToTfLiteIntArray(
|
||||
allocator, error_reporter, flatbuffer_tensor.shape(), &(result->dims)));
|
||||
} else {
|
||||
// On little-endian machines, TfLiteIntArray happens to have the same
|
||||
// memory layout as flatbuffers:Vector<int>, so we can reinterpret_cast the
|
||||
// tensor shape vector and avoid a copy.
|
||||
// TFLM doesn't allow reshaping the tensor which requires dynamic memory
|
||||
// allocation so it is safe to drop the const qualifier. In the future, if
|
||||
// we really want to update the tensor shape, we can always pass in a new
|
||||
// TfLiteIntArray - especially we have to do so if the dimension is changed.
|
||||
result->dims = const_cast<TfLiteIntArray*>(
|
||||
reinterpret_cast<const TfLiteIntArray*>(flatbuffer_tensor.shape()));
|
||||
// TfLiteIntArray - especially we have to do so if the dimension is
|
||||
TF_LITE_ENSURE_STATUS(FlatBufferVectorToTfLiteTypeArray(
|
||||
allocator, error_reporter, flatbuffer_tensor.shape(), &(result->dims)));
|
||||
}
|
||||
|
||||
// Copy the quantization information from the serialized data.
|
||||
@ -531,9 +541,9 @@ TfLiteStatus InitializeTfLiteTensorFromFlatbuffer(
|
||||
return kTfLiteError;
|
||||
}
|
||||
|
||||
// TODO(b/159043126): Check for big endian before casting flatbuffer values.
|
||||
quantization->scale = const_cast<TfLiteFloatArray*>(
|
||||
reinterpret_cast<const TfLiteFloatArray*>(src_quantization->scale()));
|
||||
TF_LITE_ENSURE_STATUS(FlatBufferVectorToTfLiteTypeArray(
|
||||
allocator, error_reporter, src_quantization->scale(),
|
||||
&quantization->scale));
|
||||
|
||||
quantization->zero_point->size = channels;
|
||||
int* zero_point_data = quantization->zero_point->data;
|
||||
@ -815,13 +825,13 @@ TfLiteStatus MicroAllocator::PrepareNodeAndRegistrationDataFromFlatbuffer(
|
||||
(void**)(&builtin_data)));
|
||||
}
|
||||
|
||||
// Disregard const qualifier to workaround with existing API.
|
||||
// TODO(b/159043126): Check for big endian before casting flatbuffer values.
|
||||
TfLiteIntArray* inputs_array = const_cast<TfLiteIntArray*>(
|
||||
reinterpret_cast<const TfLiteIntArray*>(op->inputs()));
|
||||
// TODO(b/159043126): Check for big endian before casting flatbuffer values.
|
||||
TfLiteIntArray* outputs_array = const_cast<TfLiteIntArray*>(
|
||||
reinterpret_cast<const TfLiteIntArray*>(op->outputs()));
|
||||
TfLiteIntArray* inputs_array;
|
||||
TF_LITE_ENSURE_STATUS(internal::FlatBufferVectorToTfLiteTypeArray(
|
||||
memory_allocator_, error_reporter_, op->inputs(), &inputs_array));
|
||||
|
||||
TfLiteIntArray* outputs_array;
|
||||
TF_LITE_ENSURE_STATUS(internal::FlatBufferVectorToTfLiteTypeArray(
|
||||
memory_allocator_, error_reporter_, op->outputs(), &outputs_array));
|
||||
|
||||
TfLiteNode* node = &(node_and_registrations[i].node);
|
||||
*node = {};
|
||||
|
Loading…
Reference in New Issue
Block a user