Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions extension/flat_tensor/named_data_map/TARGETS
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()
215 changes: 215 additions & 0 deletions extension/flat_tensor/named_data_map/data_map.cpp
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(
Copy link
Contributor

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.

/*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
84 changes: 84 additions & 0 deletions extension/flat_tensor/named_data_map/data_map.h
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<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im not super opinionated on this, but one thing to consider by having this state as part of the class is you force it to keep dangling around after all the method inits are done which is wasteful. I'm not really super sold on this being a huge latency win either rather then just constructing these things as they are needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this sounds good. Esp. for training case where there aren't many tensors and a linear search (or logn if tensors are sorted) isn't time consuming. If there are latency issues later down the track we can create an impl with a 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
23 changes: 23 additions & 0 deletions extension/flat_tensor/named_data_map/targets.bzl
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/...",
],
)
2 changes: 1 addition & 1 deletion extension/flat_tensor/test/TARGETS
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ load(":targets.bzl", "define_common_targets")

oncall("executorch")

define_common_targets()
define_common_targets(is_fbcode=True)

python_unittest(
name = "serialize",
Expand Down
Loading
Loading