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:
Nick Kreeger 2020-06-22 14:00:22 -07:00 committed by TensorFlower Gardener
parent 28582fb549
commit a3dc11ea13

View File

@ -392,28 +392,48 @@ TfLiteStatus CommitPlan(ErrorReporter* error_reporter, MemoryPlanner* planner,
namespace internal { namespace internal {
// Allocate a TfLiteIntArray and copy the contents of a FlatBuffers Vector // Handles architecture safe mapping of flatbuffer vectors to a TfLite*Array
// into it. // struct. Matching types are required (e.g. float and TfLiteFloatArray).
template <class T> template <typename kFlatBufferVectorType, typename kTfLiteArrayType>
TfLiteStatus FlatBufferIntArrayToTfLiteIntArray( TfLiteStatus FlatBufferVectorToTfLiteTypeArray(
SimpleMemoryAllocator* allocator, ErrorReporter* error_reporter, SimpleMemoryAllocator* allocator, ErrorReporter* error_reporter,
const flatbuffers::Vector<T>* flat_array, TfLiteIntArray** result) { const flatbuffers::Vector<kFlatBufferVectorType>* flatbuffer_array,
TfLiteIntArray* ret = kTfLiteArrayType** result) {
reinterpret_cast<TfLiteIntArray*>(allocator->AllocateFromTail( TFLITE_DCHECK(error_reporter != nullptr);
TfLiteIntArrayGetSizeInBytes(flat_array->Length()), TFLITE_DCHECK(flatbuffer_array != nullptr);
alignof(TfLiteIntArray))); // Only two conversions are supported - float and int32 - ensure that these
if (nullptr == ret) { // match at compile time instead of duplicating functions here:
TF_LITE_REPORT_ERROR( static_assert((std::is_same<kFlatBufferVectorType, int32_t>() &&
error_reporter, std::is_same<kTfLiteArrayType, TfLiteIntArray>()) ||
"Failed to allocate %d bytes of memory to copy an array.", (std::is_same<kFlatBufferVectorType, float>() &&
TfLiteIntArrayGetSizeInBytes(flat_array->Length())); std::is_same<kTfLiteArrayType, TfLiteFloatArray>()));
return kTfLiteError; 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; return kTfLiteOk;
} }
@ -469,27 +489,17 @@ TfLiteStatus InitializeTfLiteTensorFromFlatbuffer(
TF_LITE_ENSURE_STATUS(BytesRequiredForTensor( TF_LITE_ENSURE_STATUS(BytesRequiredForTensor(
flatbuffer_tensor, &result->bytes, &type_size, error_reporter)); 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) { if (flatbuffer_tensor.shape() == nullptr) {
// flatbuffer_tensor.shape() can return a nullptr in the case of a scalar // flatbuffer_tensor.shape() can return a nullptr in the case of a scalar
// tensor. // tensor.
result->dims = const_cast<TfLiteIntArray*>(&kZeroLengthIntArray); 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 { } 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 // 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 // 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 // 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. // TfLiteIntArray - especially we have to do so if the dimension is
result->dims = const_cast<TfLiteIntArray*>( TF_LITE_ENSURE_STATUS(FlatBufferVectorToTfLiteTypeArray(
reinterpret_cast<const TfLiteIntArray*>(flatbuffer_tensor.shape())); allocator, error_reporter, flatbuffer_tensor.shape(), &(result->dims)));
} }
// Copy the quantization information from the serialized data. // Copy the quantization information from the serialized data.
@ -531,9 +541,9 @@ TfLiteStatus InitializeTfLiteTensorFromFlatbuffer(
return kTfLiteError; return kTfLiteError;
} }
// TODO(b/159043126): Check for big endian before casting flatbuffer values. TF_LITE_ENSURE_STATUS(FlatBufferVectorToTfLiteTypeArray(
quantization->scale = const_cast<TfLiteFloatArray*>( allocator, error_reporter, src_quantization->scale(),
reinterpret_cast<const TfLiteFloatArray*>(src_quantization->scale())); &quantization->scale));
quantization->zero_point->size = channels; quantization->zero_point->size = channels;
int* zero_point_data = quantization->zero_point->data; int* zero_point_data = quantization->zero_point->data;
@ -815,13 +825,13 @@ TfLiteStatus MicroAllocator::PrepareNodeAndRegistrationDataFromFlatbuffer(
(void**)(&builtin_data))); (void**)(&builtin_data)));
} }
// Disregard const qualifier to workaround with existing API. TfLiteIntArray* inputs_array;
// TODO(b/159043126): Check for big endian before casting flatbuffer values. TF_LITE_ENSURE_STATUS(internal::FlatBufferVectorToTfLiteTypeArray(
TfLiteIntArray* inputs_array = const_cast<TfLiteIntArray*>( memory_allocator_, error_reporter_, op->inputs(), &inputs_array));
reinterpret_cast<const TfLiteIntArray*>(op->inputs()));
// TODO(b/159043126): Check for big endian before casting flatbuffer values. TfLiteIntArray* outputs_array;
TfLiteIntArray* outputs_array = const_cast<TfLiteIntArray*>( TF_LITE_ENSURE_STATUS(internal::FlatBufferVectorToTfLiteTypeArray(
reinterpret_cast<const TfLiteIntArray*>(op->outputs())); memory_allocator_, error_reporter_, op->outputs(), &outputs_array));
TfLiteNode* node = &(node_and_registrations[i].node); TfLiteNode* node = &(node_and_registrations[i].node);
*node = {}; *node = {};