-
Notifications
You must be signed in to change notification settings - Fork 706
[executorch][flat_tensor] DataMap implementation #7900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
87eded3
b8f4a78
eb49548
54c2137
f24fcae
b2a592f
6783e4e
4cdd830
9c37b57
7a8f1a6
8b90f0c
e1c396f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") | ||
| load(":targets.bzl", "define_common_targets") | ||
|
|
||
| oncall("executorch") | ||
|
|
||
| define_common_targets() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,215 @@ | ||
| /* | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| #include <executorch/extension/flat_tensor/named_data_map/data_map.h> | ||
| #include <executorch/extension/flat_tensor/serialize/flat_tensor_header.h> | ||
| #include <executorch/extension/flat_tensor/serialize/schema_generated.h> | ||
| #include <executorch/runtime/core/error.h> | ||
| #include <executorch/runtime/core/exec_aten/util/tensor_util.h> | ||
| #include <executorch/runtime/core/freeable_buffer.h> | ||
| #include <executorch/runtime/core/result.h> | ||
| #include <executorch/runtime/core/span.h> | ||
| #include <executorch/runtime/platform/compiler.h> | ||
|
|
||
| #include <tuple> | ||
| #include <unordered_map> | ||
|
|
||
| using executorch::runtime::Error; | ||
| using executorch::runtime::FreeableBuffer; | ||
| using executorch::runtime::Result; | ||
| using executorch::runtime::Span; | ||
|
|
||
| using executorch::aten::ScalarType; | ||
| using executorch::runtime::DataLoader; | ||
| using executorch::runtime::TensorLayout; | ||
|
|
||
| namespace executorch { | ||
| namespace extension { | ||
|
|
||
| namespace { | ||
| /** | ||
| * FlatTensor data must be aligned to this value to properly parse it. Must be a | ||
| * power of 2. Note that max_align_t is the alignment that malloc() and new | ||
| * guarantee. | ||
| */ | ||
| constexpr size_t kMinimumAlignment = alignof(std::max_align_t); | ||
|
|
||
| bool IsAligned(const void* data) { | ||
| uintptr_t addr = reinterpret_cast<uintptr_t>(data); | ||
| return addr % kMinimumAlignment == 0; | ||
| } | ||
| } // namespace | ||
|
|
||
| ET_NODISCARD Result<const TensorLayout> DataMap::get_metadata( | ||
| const char* key) const { | ||
| auto result = _name_to_tensor.find(key); | ||
| if (result == _name_to_tensor.end()) { | ||
| return Error::InvalidArgument; | ||
| } | ||
| // value is a tuple of (segment_index, offset, tensor_layout) | ||
| return std::get<2>(result->second); | ||
| } | ||
|
|
||
| ET_NODISCARD Result<FreeableBuffer> DataMap::get_data(const char* key) const { | ||
| auto result = _name_to_tensor.find(key); | ||
| if (result == _name_to_tensor.end()) { | ||
| return Error::InvalidArgument; | ||
| } | ||
| int offset = std::get<1>(result->second); | ||
| TensorLayout tensor = std::get<2>(result->second); | ||
|
|
||
| const uint8_t* data = static_cast<const uint8_t*>(_data_ro.data()) + offset; | ||
| return FreeableBuffer(data, tensor.nbytes(), nullptr); | ||
| } | ||
|
|
||
| ET_NODISCARD Result<size_t> | ||
| DataMap::load_data_into(const char* key, void* buffer, size_t size) const { | ||
| return Error::NotImplemented; | ||
| } | ||
|
|
||
| ET_NODISCARD Result<size_t> DataMap::get_num_keys() const { | ||
| return _name_to_tensor.size(); | ||
| } | ||
|
|
||
| ET_NODISCARD Result<const char*> DataMap::get_key(size_t index) const { | ||
| if (index < 0 || index >= _name_to_tensor.size()) { | ||
| return Error::InvalidArgument; | ||
| } | ||
|
|
||
| auto iter = _name_to_tensor.begin(); | ||
| for (int i = 0; i < index; ++i) { | ||
| ++iter; | ||
| } | ||
| return iter->first.c_str(); | ||
| } | ||
|
|
||
| /* static */ Result<DataMap> DataMap::load(DataLoader* loader) { | ||
| // Load data map. | ||
| size_t flatbuffer_offset = 0; | ||
| size_t flatbuffer_size = 0; | ||
| size_t segment_base_offset = 0; | ||
| size_t segment_data_size = 0; | ||
| { | ||
| // Check header. | ||
| Result<FreeableBuffer> header = loader->load( | ||
| /*offset=*/0, | ||
| FlatTensorHeader::kNumHeadBytes, | ||
| DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::External)); | ||
| if (!header.ok()) { | ||
| return header.error(); | ||
| } | ||
| Result<FlatTensorHeader> fh = | ||
| FlatTensorHeader::Parse(header->data(), header->size()); | ||
| if (fh.ok()) { | ||
| // The header has the data map size. | ||
| flatbuffer_offset = fh->flatbuffer_offset; | ||
| flatbuffer_size = fh->flatbuffer_size; | ||
| segment_base_offset = fh->segment_base_offset; | ||
| segment_data_size = fh->segment_data_size; | ||
| } else if (fh.error() == Error::NotFound) { | ||
| // No header, throw error. | ||
| ET_LOG(Error, "No FlatTensorHeader found."); | ||
| return fh.error(); | ||
| } else { | ||
| // corruption, throw error. | ||
| ET_LOG(Error, "Flat tensor header may be corrupt."); | ||
| return fh.error(); | ||
| } | ||
| } | ||
|
|
||
| // Load flatbuffer data as a segment. | ||
| Result<FreeableBuffer> flat_tensor_data = loader->load( | ||
| /*offset=*/flatbuffer_offset, | ||
| flatbuffer_size, | ||
| DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::External)); | ||
| if (!flat_tensor_data.ok()) { | ||
| return flat_tensor_data.error(); | ||
| } | ||
|
|
||
| // Make sure magic matches. | ||
| if (!flat_tensor_flatbuffer::FlatTensorBufferHasIdentifier( | ||
| flat_tensor_data->data())) { | ||
| ET_LOG( | ||
| Error, | ||
| "FlatTensor identifier '%.4s' != expected '%.4s'", | ||
| flatbuffers::GetBufferIdentifier(flat_tensor_data->data()), | ||
| flat_tensor_flatbuffer::FlatTensorIdentifier()); | ||
| return Error::InvalidExternalData; | ||
| } | ||
|
|
||
| // The flatbuffer data must start at an aligned address to ensure internal | ||
| // alignment of flatbuffer fields. | ||
| ET_CHECK_OR_RETURN_ERROR( | ||
| IsAligned(flat_tensor_data->data()), | ||
| InvalidArgument, | ||
| "FlatTensor data 0x%p must be aligned to %zu", | ||
| flat_tensor_data->data(), | ||
| kMinimumAlignment); | ||
|
|
||
| // Get pointer to root of flatbuffer table. | ||
| const flat_tensor_flatbuffer::FlatTensor* flat_tensor = | ||
| flat_tensor_flatbuffer::GetFlatTensor(flat_tensor_data->data()); | ||
|
|
||
| // Get pointer to tensor metadata. | ||
| const auto* s_tensor_metadata = flat_tensor->tensors(); | ||
| assert(s_tensor_metadata != nullptr); | ||
|
|
||
| std::unordered_map<std::string, std::tuple<int, int, TensorLayout>> | ||
| name_to_tensor = {}; | ||
| for (int i = 0; i < s_tensor_metadata->size(); i++) { | ||
| // Create TensorLayouts. | ||
| ScalarType scalar_type = | ||
| static_cast<ScalarType>(s_tensor_metadata->Get(i)->scalar_type()); | ||
| const int dim = s_tensor_metadata->Get(i)->sizes()->size(); | ||
|
|
||
| const auto serialized_sizes = s_tensor_metadata->Get(i)->sizes()->data(); | ||
| const auto serialized_dim_order = | ||
| s_tensor_metadata->Get(i)->dim_order()->data(); | ||
| TensorLayout tensor_layout = TensorLayout( | ||
| scalar_type, | ||
| Span<const int32_t>(serialized_sizes, dim), | ||
| Span<const uint8_t>(serialized_dim_order, dim)); | ||
|
|
||
| int segment_index = s_tensor_metadata->Get(i)->segment_index(); | ||
| int offset = s_tensor_metadata->Get(i)->offset(); | ||
| std::string key = s_tensor_metadata->Get(i)->fully_qualified_name()->str(); | ||
|
|
||
| auto val = std::make_tuple(segment_index, offset, tensor_layout); | ||
| name_to_tensor.insert({key, std::move(val)}); | ||
| } | ||
|
|
||
| // Load constant data. | ||
| const auto* s_data_segment = flat_tensor->segments(); | ||
|
|
||
| // Only support one segment for now. | ||
| assert(s_data_segment->size() == 1); | ||
| // First segment offset should be 0. | ||
| int segment_offset = s_data_segment->Get(0)->offset(); | ||
| assert(segment_offset == 0); | ||
| // First segment size should be <= the total segment data size. | ||
| int segment_size = s_data_segment->Get(0)->size(); | ||
| assert(segment_size <= segment_data_size); | ||
|
|
||
| Result<FreeableBuffer> _data_ro = loader->load( | ||
| /*offset=*/segment_base_offset + segment_offset, | ||
| segment_size, | ||
| DataLoader::SegmentInfo(DataLoader::SegmentInfo::Type::External)); | ||
| if (!_data_ro.ok()) { | ||
| return _data_ro.error(); | ||
| } | ||
|
|
||
| return DataMap( | ||
| std::move(flat_tensor_data.get()), | ||
| std::move(name_to_tensor), | ||
| std::move(_data_ro.get())); | ||
| } | ||
|
|
||
| DataMap::~DataMap() {} | ||
|
|
||
| } // namespace extension | ||
| } // namespace executorch | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| /* | ||
| * Copyright (c) Meta Platforms, Inc. and affiliates. | ||
| * All rights reserved. | ||
| * | ||
| * This source code is licensed under the BSD-style license found in the | ||
| * LICENSE file in the root directory of this source tree. | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <executorch/runtime/core/data_loader.h> | ||
| #include <executorch/runtime/core/exec_aten/exec_aten.h> | ||
| #include <executorch/runtime/core/named_data_map.h> | ||
| #include <executorch/runtime/core/result.h> | ||
| #include <executorch/runtime/core/tensor_layout.h> | ||
| #include <executorch/runtime/platform/compiler.h> | ||
|
|
||
| #include <unordered_map> | ||
| #include <utility> | ||
|
|
||
| // Forward declare flatbuffer types. This is a public header and must not | ||
| // include the generated flatbuffer header. | ||
| namespace flat_tensor_flatbuffer { | ||
| struct FlatTensor; | ||
| } // namespace flat_tensor | ||
|
|
||
| namespace executorch { | ||
| namespace extension { | ||
|
|
||
| class DataMap final : public executorch::runtime::NamedDataMap { | ||
| public: | ||
| static executorch::runtime::Result<DataMap> load( | ||
| executorch::runtime::DataLoader* loader); | ||
|
|
||
| ET_NODISCARD | ||
| executorch::runtime::Result<const executorch::runtime::TensorLayout> | ||
| get_metadata(const char* key) const override; | ||
| ET_NODISCARD | ||
| executorch::runtime::Result<executorch::runtime::FreeableBuffer> get_data( | ||
| const char* key) const override; | ||
| ET_NODISCARD executorch::runtime::Result<size_t> | ||
| load_data_into(const char* key, void* buffer, size_t size) const override; | ||
|
|
||
| ET_NODISCARD executorch::runtime::Result<size_t> get_num_keys() | ||
| const override; | ||
| ET_NODISCARD executorch::runtime::Result<const char*> get_key( | ||
| size_t index) const override; | ||
|
|
||
| DataMap(DataMap&&) noexcept = default; | ||
| ~DataMap() override; | ||
|
|
||
| private: | ||
| DataMap( | ||
| executorch::runtime::FreeableBuffer&& flat_tensor_data, | ||
| std::unordered_map< | ||
| std::string, | ||
| std::tuple<int, int, executorch::runtime::TensorLayout>> | ||
| name_to_tensor, | ||
| executorch::runtime::FreeableBuffer&& data_ro) | ||
| : _flat_tensor_data(std::move(flat_tensor_data)), | ||
| _name_to_tensor(std::move(name_to_tensor)), | ||
| _data_ro(std::move(data_ro)) {} | ||
|
|
||
| // Not copyable or assignable. | ||
| DataMap(const DataMap& rhs) = delete; | ||
| DataMap& operator=(DataMap&& rhs) noexcept = delete; | ||
| DataMap& operator=(const DataMap& rhs) = delete; | ||
|
|
||
| // FlatTensor flatbuffer data. Contains the data backing up | ||
| // TensorLayout information in the _name_to_tensor map; must outlive it. | ||
| executorch::runtime::FreeableBuffer _flat_tensor_data; | ||
|
|
||
| // Map of name to {segment index, offset, TensorLayout}. | ||
| std::unordered_map< | ||
|
||
| std::string, | ||
| std::tuple<int, int, executorch::runtime::TensorLayout>> | ||
| _name_to_tensor; | ||
|
|
||
| // Raw, read-only tensor data. | ||
| executorch::runtime::FreeableBuffer _data_ro; | ||
| }; | ||
|
|
||
| } // namespace extension | ||
| } // namespace executorch | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| load("@fbsource//xplat/executorch/build:runtime_wrapper.bzl", "runtime") | ||
|
|
||
| def define_common_targets(): | ||
| runtime.cxx_library( | ||
| name = "data_map", | ||
| srcs = [ | ||
| "data_map.cpp", | ||
| ], | ||
| exported_headers = ["data_map.h"], | ||
| deps = [ | ||
| "//executorch/extension/flat_tensor/serialize:schema", | ||
| "//executorch/extension/flat_tensor/serialize:serialize", | ||
| "//executorch/extension/flat_tensor/serialize:generated_headers", | ||
| "//executorch/extension/flat_tensor/serialize:flat_tensor_header", | ||
| "//executorch/runtime/core:core", | ||
| "//executorch/runtime/core:evalue", | ||
| "//executorch/runtime/core/exec_aten:lib", | ||
| "//executorch/runtime/core/exec_aten/util:tensor_util", | ||
| ], | ||
| visibility = [ | ||
| "//executorch/...", | ||
| ], | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I was trying to figure out how I wanted to handle this for mutable state. Here is what Ive come up with.
Lazily load segments, but internally keep a list of all the loaded segments. If the first time you need to load a segment is through "get_data" then put it in the preserve lis. If the first time you need to load a segment is through load_into then instead you just call data_loader load into which doesnt keep it hanging around.