| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #include "edge-impulse-sdk/tensorflow/lite/micro/micro_allocation_info.h" |
|
|
| #include <algorithm> |
|
|
| #include "edge-impulse-sdk/tensorflow/lite/c/c_api_types.h" |
| #include "edge-impulse-sdk/tensorflow/lite/kernels/internal/compatibility.h" |
| #include "edge-impulse-sdk/tensorflow/lite/kernels/kernel_util.h" |
| #include "edge-impulse-sdk/tensorflow/lite/micro/memory_helpers.h" |
| #include "edge-impulse-sdk/tensorflow/lite/micro/memory_planner/greedy_memory_planner.h" |
| #include "edge-impulse-sdk/tensorflow/lite/micro/micro_log.h" |
|
|
| namespace tflite { |
|
|
| namespace { |
| constexpr char kOfflineMemAllocMetadata[] = "OfflineMemoryAllocation"; |
| constexpr int kUninitializedLifetime = -1; |
| } |
|
|
| |
| |
| |
| void AllocationInfoBuilder::UpdateFirstCreated(AllocationInfo* current, |
| int allocation_scope_count) { |
| TFLITE_DCHECK(current->first_created <= allocation_scope_count); |
| if (current->first_created == kUninitializedLifetime) { |
| current->first_created = allocation_scope_count; |
| } |
| } |
|
|
| |
| |
| |
| void AllocationInfoBuilder::UpdateLastUsed(AllocationInfo* current, |
| int allocation_scope_count) { |
| TFLITE_DCHECK(current->last_used <= allocation_scope_count); |
| current->last_used = allocation_scope_count; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::MarkSubgraphLifetimesIfNecessary( |
| const Operator* op, internal::ScratchBufferRequest* scratch_buffer_requests, |
| ScratchBufferHandle* scratch_buffer_handles, |
| SubgraphAllocations* allocations) { |
| int first_subgraph_index = -1; |
| int second_subgraph_index = -1; |
| const OperatorCode* opcode = |
| model_->operator_codes()->Get(op->opcode_index()); |
| switch (opcode->builtin_code()) { |
| case BuiltinOperator_IF: { |
| first_subgraph_index = |
| op->builtin_options_as_IfOptions()->then_subgraph_index(); |
| second_subgraph_index = |
| op->builtin_options_as_IfOptions()->else_subgraph_index(); |
| break; |
| } |
| case BuiltinOperator_CALL_ONCE: { |
| first_subgraph_index = |
| op->builtin_options_as_CallOnceOptions()->init_subgraph_index(); |
| break; |
| } |
| case BuiltinOperator_WHILE: { |
| first_subgraph_index = |
| op->builtin_options_as_WhileOptions()->cond_subgraph_index(); |
| second_subgraph_index = |
| op->builtin_options_as_WhileOptions()->body_subgraph_index(); |
| break; |
| } |
| default: { |
| break; |
| } |
| } |
| if (first_subgraph_index != -1) { |
| |
| allocation_scope_count_++; |
| TF_LITE_ENSURE_STATUS( |
| MarkAllocationLifetimes(first_subgraph_index, scratch_buffer_requests, |
| scratch_buffer_handles, allocations)); |
| } |
| if (second_subgraph_index != -1) { |
| |
| allocation_scope_count_++; |
| TF_LITE_ENSURE_STATUS( |
| MarkAllocationLifetimes(second_subgraph_index, scratch_buffer_requests, |
| scratch_buffer_handles, allocations)); |
| } |
| return kTfLiteOk; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::CreateAllocationInfo( |
| int scratch_buffer_request_count) { |
| size_t subgraph_offsets_length = model_->subgraphs()->size() * sizeof(size_t); |
| info_.subgraph_offsets = |
| reinterpret_cast<size_t*>(non_persistent_allocator_->AllocateTemp( |
| subgraph_offsets_length, alignof(size_t))); |
| if (info_.subgraph_offsets == nullptr) { |
| MicroPrintf( |
| "Failed to allocate memory for memory planning, %d bytes required", |
| subgraph_offsets_length); |
| return kTfLiteError; |
| } |
| size_t tensor_count = 0; |
| for (size_t subgraph_idx = 0; subgraph_idx < model_->subgraphs()->size(); |
| subgraph_idx++) { |
| |
| |
| |
| info_.subgraph_offsets[subgraph_idx] = tensor_count; |
| tensor_count += model_->subgraphs()->Get(subgraph_idx)->tensors()->size(); |
| } |
| info_.tensor_count = tensor_count; |
|
|
| |
| |
| info_.scratch_offset = tensor_count; |
| info_.allocation_info_count = tensor_count + scratch_buffer_request_count; |
| info_.scratch_buffer_count = scratch_buffer_request_count; |
| size_t bytes = sizeof(AllocationInfo) * info_.allocation_info_count; |
|
|
| |
| |
| info_.allocation_info = reinterpret_cast<AllocationInfo*>( |
| non_persistent_allocator_->AllocateTemp(bytes, alignof(AllocationInfo))); |
| if (info_.allocation_info == nullptr) { |
| MicroPrintf( |
| "Failed to allocate memory for memory planning, %d bytes required", |
| bytes); |
| return kTfLiteError; |
| } |
| return kTfLiteOk; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::FreeAllocationInfo() { |
| non_persistent_allocator_->DeallocateTemp( |
| reinterpret_cast<uint8_t*>(info_.allocation_info)); |
| non_persistent_allocator_->DeallocateTemp( |
| reinterpret_cast<uint8_t*>(info_.subgraph_offsets)); |
| return kTfLiteOk; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::ValidateSubgraph( |
| const SubGraph* subgraph, TfLiteEvalTensor* eval_tensors) { |
| uint32_t operators_size = NumSubgraphOperators(subgraph); |
|
|
| for (uint32_t i = 0; i < operators_size; i++) { |
| const auto op = subgraph->operators()->Get(i); |
| for (size_t n = 0; |
| op->intermediates() != nullptr && n < op->intermediates()->size(); |
| n++) { |
| const int tensor_index = op->intermediates()->Get(n); |
| size_t tensor_size = -1; |
| TF_LITE_ENSURE_STATUS(TfLiteEvalTensorByteLength( |
| &eval_tensors[tensor_index], &tensor_size)); |
| if (tensor_size != 0) { |
| MicroPrintf( |
| "Does not support intermediate tensor with non-zero size: %d", |
| tensor_size); |
| return kTfLiteError; |
| } |
| } |
| } |
| return kTfLiteOk; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::InitializeAllocationInfo( |
| const int32_t* offline_offsets, SubgraphAllocations* allocations) { |
| AllocationInfo* allocation_info = info_.allocation_info; |
| |
| for (size_t subgraph_idx = 0; subgraph_idx < model_->subgraphs()->size(); |
| subgraph_idx++) { |
| const SubGraph* subgraph = model_->subgraphs()->Get(subgraph_idx); |
| TfLiteEvalTensor* eval_tensors = allocations[subgraph_idx].tensors; |
| AllocationInfo* subgraph_allocation_info = |
| &allocation_info[info_.subgraph_offsets[subgraph_idx]]; |
|
|
| |
| TF_LITE_ENSURE_STATUS(ValidateSubgraph(subgraph, eval_tensors)); |
|
|
| for (size_t i = 0; i < subgraph->tensors()->size(); ++i) { |
| AllocationInfo* current = &subgraph_allocation_info[i]; |
| current->output_ptr = &(eval_tensors[i].data.data); |
|
|
| TF_LITE_ENSURE_STATUS( |
| TfLiteEvalTensorByteLength(&eval_tensors[i], ¤t->bytes)); |
|
|
| current->first_created = kUninitializedLifetime; |
| current->last_used = kUninitializedLifetime; |
| current->needs_allocating = |
| (eval_tensors[i].data.data == nullptr) && |
| (!subgraph->tensors()->Get(i)->is_variable()) && |
| (current->bytes != 0); |
| if (offline_offsets) { |
| current->offline_offset = offline_offsets[i]; |
|
|
| |
| |
| if (subgraph->tensors()->Get(i)->is_variable() && |
| current->offline_offset != kOnlinePlannedBuffer) { |
| current->needs_allocating = true; |
| } |
|
|
| } else { |
| current->offline_offset = kOnlinePlannedBuffer; |
| } |
| } |
| } |
| |
| AllocationInfo* scratch_allocation_info = |
| &allocation_info[info_.scratch_offset]; |
| for (size_t i = 0; i < info_.scratch_buffer_count; i++) { |
| AllocationInfo* current = &scratch_allocation_info[i]; |
| current->first_created = kUninitializedLifetime; |
| current->last_used = kUninitializedLifetime; |
| current->needs_allocating = true; |
| current->offline_offset = kOnlinePlannedBuffer; |
| } |
| return kTfLiteOk; |
| } |
|
|
| TfLiteStatus AllocationInfoBuilder::MarkAllocationLifetimes( |
| int subgraph_idx, internal::ScratchBufferRequest* scratch_buffer_requests, |
| ScratchBufferHandle* scratch_buffer_handles, |
| SubgraphAllocations* allocations) { |
| const SubGraph* subgraph = model_->subgraphs()->Get(subgraph_idx); |
|
|
| AllocationInfo* allocation_info = info_.allocation_info; |
| |
| |
| AllocationInfo* subgraph_allocation_info = |
| &allocation_info[info_.subgraph_offsets[subgraph_idx]]; |
|
|
| uint32_t operators_size = NumSubgraphOperators(subgraph); |
| |
| for (size_t i = 0; |
| subgraph->inputs() != nullptr && i < subgraph->inputs()->size(); ++i) { |
| const int tensor_index = subgraph->inputs()->Get(i); |
| AllocationInfo* current = &subgraph_allocation_info[tensor_index]; |
| UpdateFirstCreated(current, allocation_scope_count_); |
| |
| |
| UpdateLastUsed(current, allocation_scope_count_); |
| } |
|
|
| for (uint32_t i = 0; i < operators_size; i++) { |
| |
| allocation_scope_count_++; |
| const auto* op = subgraph->operators()->Get(i); |
| |
| for (size_t n = 0; op->outputs() != nullptr && n < op->outputs()->size(); |
| ++n) { |
| const int tensor_index = op->outputs()->Get(n); |
| AllocationInfo* current = &subgraph_allocation_info[tensor_index]; |
| UpdateFirstCreated(current, allocation_scope_count_); |
| } |
|
|
| |
| |
| int start_allocation_scope_count = allocation_scope_count_; |
|
|
| |
| |
| MarkSubgraphLifetimesIfNecessary(op, scratch_buffer_requests, |
| scratch_buffer_handles, allocations); |
|
|
| |
| for (size_t n = 0; op->inputs() != nullptr && n < op->inputs()->size(); |
| ++n) { |
| const int tensor_index = op->inputs()->Get(n); |
| |
| if (tensor_index >= 0) { |
| AllocationInfo* current = &subgraph_allocation_info[tensor_index]; |
| |
| |
| |
| UpdateLastUsed(current, allocation_scope_count_); |
| } |
| } |
| for (size_t n = 0; op->outputs() != nullptr && n < op->outputs()->size(); |
| ++n) { |
| const int tensor_index = op->outputs()->Get(n); |
| AllocationInfo* current = &subgraph_allocation_info[tensor_index]; |
| UpdateLastUsed(current, allocation_scope_count_); |
| } |
|
|
| |
| |
| |
| |
| AllocationInfo* scratch_allocation_info = |
| &allocation_info[info_.scratch_offset]; |
| for (size_t scratch_idx = 0; scratch_idx < info_.scratch_buffer_count; |
| scratch_idx++) { |
| internal::ScratchBufferRequest request = |
| scratch_buffer_requests[scratch_idx]; |
| AllocationInfo* current = &scratch_allocation_info[scratch_idx]; |
| if (request.node_idx == static_cast<int>(i) && |
| request.subgraph_idx == static_cast<int>(subgraph_idx)) { |
| ScratchBufferHandle* current_handle = |
| &(scratch_buffer_handles[scratch_idx]); |
| current->output_ptr = reinterpret_cast<void**>(¤t_handle->data); |
| current->bytes = request.bytes; |
| UpdateFirstCreated(current, start_allocation_scope_count); |
| UpdateLastUsed(current, allocation_scope_count_); |
| } |
| } |
| } |
|
|
| |
| for (size_t i = 0; |
| subgraph->outputs() != nullptr && i < subgraph->outputs()->size(); ++i) { |
| const int tensor_index = subgraph->outputs()->Get(i); |
| AllocationInfo* current = &subgraph_allocation_info[tensor_index]; |
| |
| |
| |
| |
| UpdateFirstCreated(current, allocation_scope_count_); |
| UpdateLastUsed(current, allocation_scope_count_); |
| } |
| return kTfLiteOk; |
| } |
|
|
| |
| |
| TfLiteStatus AllocationInfoBuilder::GetOfflinePlannedOffsets( |
| const int32_t** offline_planner_offsets) { |
| if (model_->metadata()) { |
| for (size_t i = 0; i < model_->metadata()->size(); ++i) { |
| auto metadata = model_->metadata()->Get(i); |
|
|
| if (metadata->name()) { |
| const size_t metadata_name_size = metadata->name()->size(); |
|
|
| if ((strncmp(metadata->name()->c_str(), kOfflineMemAllocMetadata, |
| std::min(metadata_name_size, |
| strlen(kOfflineMemAllocMetadata))) == 0) && |
| metadata_name_size == strlen(kOfflineMemAllocMetadata)) { |
| const flatbuffers::Vector<flatbuffers::Offset<Buffer>>* buffers = |
| model_->buffers(); |
| auto* buffer = (*buffers)[metadata->buffer()]; |
| auto* array = buffer->data(); |
| const uint32_t* metadata_buffer = |
| reinterpret_cast<const uint32_t*>(array->data()); |
| const size_t nbr_tensors = static_cast<size_t>(metadata_buffer[2]); |
| *offline_planner_offsets = |
| reinterpret_cast<const int32_t*>(&metadata_buffer[3]); |
|
|
| if (info_.tensor_count != nbr_tensors) { |
| MicroPrintf( |
| "Nbr of offline buffer offsets (%d) in metadata " |
| "not equal nbr tensors (%d)\n", |
| nbr_tensors, info_.tensor_count); |
| return kTfLiteError; |
| } |
| } |
| } |
| } |
| } |
| return kTfLiteOk; |
| } |
|
|
| } |
|
|