Skip to content

Commit

Permalink
Handle zero-sized TFLite tensor allocations
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 200277562
  • Loading branch information
tensorflower-gardener committed Jun 12, 2018
1 parent 52af244 commit 85c518b
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 8 deletions.
7 changes: 2 additions & 5 deletions tensorflow/contrib/lite/arena_planner_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,8 @@ TEST_F(ArenaPlannerTest, ZeroSizedTensors) {
TestGraph graph({1}, {{{1}, {2}, {}}}, {2});
(*graph.tensors())[1].bytes = 0;
SetGraph(&graph);
// TODO(ahentz): this is currently broken because the arena finds two
// allocations with the same offset and returns an error.
ASSERT_FALSE(planner_->ExecuteAllocations(0, 10) == kTfLiteOk);
// EXPECT_EQ(GetOffset(1), 0);
// EXPECT_EQ(GetOffset(2), GetOffsetAfter(1));
ASSERT_EQ(planner_->ExecuteAllocations(0, 10), kTfLiteOk);
EXPECT_EQ((*graph_->tensors())[1].data.raw, nullptr);
}

TEST_F(ArenaPlannerTest, SimpleGraph) {
Expand Down
15 changes: 14 additions & 1 deletion tensorflow/contrib/lite/interpreter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ TEST(BasicInterpreter, CheckArenaAllocation) {
TfLiteRegistration reg = {nullptr, nullptr, nullptr, nullptr};

std::vector<int> sizes{2048, 4096, 1023, 2047, 1021,
2047, 1023, 2046, 1021, 2048};
2047, 1023, 2046, 0, 2048};
for (int i = 0; i < sizes.size(); ++i) {
interpreter.SetTensorParametersReadWrite(i, kTfLiteUInt8, "", {sizes[i]},
quant);
Expand All @@ -228,6 +228,7 @@ TEST(BasicInterpreter, CheckArenaAllocation) {

ASSERT_EQ(interpreter.tensor(0)->data.raw, interpreter.tensor(4)->data.raw);
ASSERT_EQ(interpreter.tensor(1)->data.raw, interpreter.tensor(7)->data.raw);
ASSERT_EQ(interpreter.tensor(8)->data.raw, nullptr);

ASSERT_LT(interpreter.tensor(4)->data.raw, interpreter.tensor(1)->data.raw);
ASSERT_LT(interpreter.tensor(6)->data.raw, interpreter.tensor(1)->data.raw);
Expand Down Expand Up @@ -314,6 +315,18 @@ TEST(BasicInterpreter, ResizingTensors) {
EXPECT_EQ(tensor->bytes, 8 * sizeof(float));
ASSERT_EQ(interpreter.AllocateTensors(), kTfLiteOk);

ASSERT_EQ(interpreter.ResizeInputTensor(t, {}), kTfLiteOk);
EXPECT_EQ(tensor->bytes, 1 * sizeof(float));
ASSERT_EQ(interpreter.AllocateTensors(), kTfLiteOk);

ASSERT_EQ(interpreter.ResizeInputTensor(t, {0}), kTfLiteOk);
EXPECT_EQ(tensor->bytes, 0);
ASSERT_EQ(interpreter.AllocateTensors(), kTfLiteOk);

ASSERT_EQ(interpreter.ResizeInputTensor(t, {1, 2, 0}), kTfLiteOk);
EXPECT_EQ(tensor->bytes, 0);
ASSERT_EQ(interpreter.AllocateTensors(), kTfLiteOk);

// TODO(ahentz): We shouldn't have to force reallocation, but
// ResizeInputTensor doesn't realloc dynamic tensors. Also note that
// TfLiteTensorRealloc(tensor->bytes, tensor) is a no-op.
Expand Down
16 changes: 15 additions & 1 deletion tensorflow/contrib/lite/simple_memory_arena.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ TfLiteStatus SimpleMemoryArena::Allocate(TfLiteContext* context,
ArenaAlloc* new_alloc) {
TF_LITE_ENSURE(context, alignment < arena_alignment_);

if (size == 0) {
new_alloc->offset = 0;
new_alloc->size = 0;
return kTfLiteOk;
}

size_t current_top = 0;

if (!allocs_.empty()) {
Expand Down Expand Up @@ -75,6 +81,10 @@ TfLiteStatus SimpleMemoryArena::Allocate(TfLiteContext* context,

TfLiteStatus SimpleMemoryArena::Deallocate(TfLiteContext* context,
const ArenaAlloc& alloc) {
if (alloc.size == 0) {
return kTfLiteOk;
}

int erased_allocs_count = 0;
auto it = allocs_.begin();
while (it != allocs_.end()) {
Expand Down Expand Up @@ -122,7 +132,11 @@ TfLiteStatus SimpleMemoryArena::ResolveAlloc(TfLiteContext* context,
char** output_ptr) {
TF_LITE_ENSURE(context, committed_);
TF_LITE_ENSURE(context, output_ptr != nullptr);
*output_ptr = underlying_buffer_aligned_ptr_ + alloc.offset;
if (alloc.size == 0) {
*output_ptr = nullptr;
} else {
*output_ptr = underlying_buffer_aligned_ptr_ + alloc.offset;
}
return kTfLiteOk;
}

Expand Down
3 changes: 2 additions & 1 deletion tensorflow/contrib/lite/simple_memory_arena.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ struct ArenaAlloc {
// This small class is responsible for allocating, deallocating and reusing
// dynamic memory from a common underlying buffer. The arena can be used in
// scenarios when the pattern of memory allocations and deallocations is
// repetitive, e.g. running NN inference in multiple iterations.
// repetitive, e.g. running NN inference in multiple iterations. Note that
// zero-sized allocations are explicitly allowed, and will resolve to null.
class SimpleMemoryArena {
public:
explicit SimpleMemoryArena(size_t arena_alignment)
Expand Down
41 changes: 41 additions & 0 deletions tensorflow/contrib/lite/simple_memory_arena_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,47 @@ TEST(SimpleMemoryArenaTest, BasicArenaOperations) {
EXPECT_EQ(allocs[5].offset, 1024);
}

TEST(SimpleMemoryArenaTest, BasicZeroAlloc) {
TfLiteContext context;
SimpleMemoryArena arena(64);
ArenaAlloc alloc;

// Zero-sized allocs should have a 0 offset and size.
ASSERT_EQ(arena.Allocate(&context, 32, 0, &alloc), kTfLiteOk);
EXPECT_EQ(alloc.offset, 0);
EXPECT_EQ(alloc.size, 0);

// Deallocation of zero-sized allocs should always succeed (even redundantly).
ASSERT_EQ(arena.Deallocate(&context, alloc), kTfLiteOk);
ASSERT_EQ(arena.Deallocate(&context, alloc), kTfLiteOk);

// The zero-sized alloc should resolve to null.
char* resolved_ptr = nullptr;
ASSERT_EQ(arena.Commit(&context), kTfLiteOk);
ASSERT_EQ(arena.ResolveAlloc(&context, alloc, &resolved_ptr), kTfLiteOk);
EXPECT_EQ(resolved_ptr, nullptr);
}

TEST(SimpleMemoryArenaTest, InterleavedZeroAlloc) {
TfLiteContext context;
SimpleMemoryArena arena(64);
ArenaAlloc allocs[4];

// Interleave some zero and non-zero-sized allocations and deallocations.
ASSERT_EQ(arena.Allocate(&context, 32, 2047, &allocs[0]), kTfLiteOk);
ASSERT_EQ(arena.Allocate(&context, 32, 0, &allocs[1]), kTfLiteOk);
ASSERT_EQ(arena.Allocate(&context, 32, 1023, &allocs[2]), kTfLiteOk);
ASSERT_EQ(arena.Deallocate(&context, allocs[1]), kTfLiteOk);
ASSERT_EQ(arena.Deallocate(&context, allocs[2]), kTfLiteOk);
ASSERT_EQ(arena.Allocate(&context, 32, 2047, &allocs[3]), kTfLiteOk);

// Deallocation of a zero-sized alloc should not impact the allocator offsets.
EXPECT_EQ(allocs[0].offset, 0);
EXPECT_EQ(allocs[1].offset, 0);
EXPECT_EQ(allocs[2].offset, 2048);
EXPECT_EQ(allocs[3].offset, 2048);
}

TEST(SimpleMemoryArenaTest, TestAfterClear) {
TfLiteContext context;
SimpleMemoryArena arena(64);
Expand Down

0 comments on commit 85c518b

Please sign in to comment.