Skip to content
4 changes: 3 additions & 1 deletion extension/flat_tensor/targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ def define_common_targets():
"flat_tensor_data_map.cpp",
],
exported_headers = ["flat_tensor_data_map.h"],
exported_deps = [
"//executorch/extension/flat_tensor/serialize:flat_tensor_header",
],
deps = [
"//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:named_data_map",
Expand Down
150 changes: 139 additions & 11 deletions runtime/executor/method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,118 @@ Result<bool> parse_cond_value(const EValue& cond_value) {

} // namespace

Result<size_t> Method::get_num_external_constants() {
auto flatbuffer_values = serialization_plan_->values();
size_t n_value = flatbuffer_values->size();

size_t num_external_constants = 0;
for (size_t i = 0; i < n_value; ++i) {
auto serialization_value = flatbuffer_values->Get(i);
// Ensure that the `val_as_X()` calls will return non-null pointers.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@dbort I moved the check here, as this is now the first iteration over the flatbuffer values.

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 see. In this case, I'd remove the comment here, since it doesn't actually describe the check's purpose: in this case, it's just ensuring that the value is non-null so it can be used here. "val_as_X()" isn't used in this function, so the comment will seem incongruous to readers.

Or you could reword it like "Note that as a side-effect of this check, we're guaranteed that all values are non-null, so later loops can skip that check."

ET_CHECK_OR_RETURN_ERROR(
serialization_value != nullptr &&
(serialization_value->val_type() ==
executorch_flatbuffer::KernelTypes::Null ||
serialization_value->val() != nullptr),
InvalidProgram,
"Null value at index %" ET_PRIsize_t,
i);
// Ignore non-tensor types.
if (serialization_value->val_type() !=
executorch_flatbuffer::KernelTypes::Tensor) {
continue;
}
const auto s_tensor = static_cast<const executorch_flatbuffer::Tensor*>(
serialization_value->val());

// An external constant is tagged with EXTERNAL and has no
// allocation_info.
if (s_tensor->extra_tensor_info() != nullptr &&
s_tensor->extra_tensor_info()->location() ==
executorch_flatbuffer::TensorDataLocation::EXTERNAL &&
s_tensor->allocation_info() == nullptr) {
num_external_constants++;
}
}
return num_external_constants;
}

bool key_exists(const char* key, NamedData* external_constants, int num_keys) {
for (int i = 0; i < num_keys; i++) {
if (strcmp(key, external_constants[i].key) == 0) {
return true;
}
}
return false;
}

Error Method::parse_external_constants(const NamedDataMap* named_data_map) {
auto flatbuffer_values = serialization_plan_->values();
size_t n_value = flatbuffer_values->size();

// The number of unique external tensors that have been resolved.
int index = 0;
for (size_t i = 0; i < n_value; ++i) {
auto serialization_value = flatbuffer_values->Get(i);
// Ignore non-tensor types.
if (serialization_value->val_type() !=
executorch_flatbuffer::KernelTypes::Tensor) {
continue;
}
const auto s_tensor = static_cast<const executorch_flatbuffer::Tensor*>(
serialization_value->val());
// Constant tensors are resolved here; tensors with allocation_info are
// mutable and are resolved in parse_values.
if (s_tensor->extra_tensor_info() != nullptr &&
s_tensor->extra_tensor_info()->location() ==
executorch_flatbuffer::TensorDataLocation::EXTERNAL &&
s_tensor->allocation_info() == nullptr) {
ET_CHECK_OR_RETURN_ERROR(
s_tensor->extra_tensor_info()->fully_qualified_name() != nullptr,
InvalidExternalData,
"Fully qualified name of external tensor is null");

const char* key =
s_tensor->extra_tensor_info()->fully_qualified_name()->c_str();

// Check if this tensor has already been resolved.
if (!key_exists(key, external_constants_, index)) {
Result<const TensorLayout> tensor_layout =
named_data_map->get_metadata(key);
if (!tensor_layout.ok()) {
return tensor_layout.error();
}
// Check external tensor compatibility.
Error err = deserialization::validateExternalTensor(
s_tensor, tensor_layout.get());
if (err != Error::Ok) {
return err;
}
// Save the key.
external_constants_[index].key = key;

// Save the buffer.
Result<FreeableBuffer> buffer = named_data_map->get_data(key);
ET_CHECK_OR_RETURN_ERROR(
buffer.ok(),
InvalidExternalData,
"Buffer retrieved from get_data is not valid");
external_constants_[index].buffer =
memory_manager_->method_allocator()
->allocateInstance<FreeableBuffer>();
if (external_constants_[index].buffer == nullptr) {
return Error::MemoryAllocationFailed;
}
new (external_constants_[index].buffer)
FreeableBuffer(std::move(buffer.get()));

index++;
}
}
}
return Error::Ok;
}

Error Method::parse_values(const NamedDataMap* named_data_map) {
auto flatbuffer_values = serialization_plan_->values();
ET_CHECK_OR_RETURN_ERROR(
Expand All @@ -299,23 +411,33 @@ Error Method::parse_values(const NamedDataMap* named_data_map) {
return Error::MemoryAllocationFailed;
}

// Check if there are any external constants.
Result<size_t> num_external_constants = get_num_external_constants();
if (!num_external_constants.ok()) {
return num_external_constants.error();
}
num_external_constants_ = *num_external_constants;
if (num_external_constants_ > 0) {
// Allocate space for external tensors.
external_constants_ =
memory_manager_->method_allocator()->allocateList<NamedData>(
num_external_constants_);
if (external_constants_ == nullptr) {
return Error::MemoryAllocationFailed;
}
auto err = parse_external_constants(named_data_map);
if (err != Error::Ok) {
return err;
}
}

// n_value_ counts the number of successfully-initialized values for ~Method()
// to clean up, and is incremented at the bottom of the loop. This makes it
// safe for errors to return without updating any state.
n_value_ = 0;

for (size_t i = 0; i < n_value; ++i) {
auto serialization_value = flatbuffer_values->Get(i);
// Ensure that the `val_as_X()` calls will return non-null pointers.
ET_CHECK_OR_RETURN_ERROR(
serialization_value != nullptr &&
(serialization_value->val_type() ==
executorch_flatbuffer::KernelTypes::Null ||
serialization_value->val() != nullptr),
InvalidProgram,
"Null value at index %" ET_PRIsize_t,
i);

const auto val = serialization_value->val();

switch (serialization_value->val_type()) {
Expand Down Expand Up @@ -416,7 +538,8 @@ Error Method::parse_values(const NamedDataMap* named_data_map) {
program_,
memory_manager_,
static_cast<const executorch_flatbuffer::Tensor*>(val),
named_data_map);
named_data_map,
Span<NamedData>(external_constants_, num_external_constants_));
if (!t.ok()) {
ET_LOG(
Error,
Expand Down Expand Up @@ -1496,6 +1619,11 @@ Method::~Method() {
delegates_[i].~BackendDelegate();
}
}
// Free resources associated with external constants.
for (int i = 0; i < num_external_constants_; i++) {
external_constants_[i].buffer->Free();
external_constants_[i].buffer = nullptr;
}
// All other fields are trivially destructible.
}
} // namespace runtime
Expand Down
38 changes: 36 additions & 2 deletions runtime/executor/method.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ using OpFunction = void (*)(KernelRuntimeContext&, EValue**);
/// argument list for a single instruction
using InstructionArgs = Span<EValue*>;

/// Data structure to hold key and data buffer for external data used
/// in a method.
struct NamedData {
const char* key;
FreeableBuffer* buffer;
};

// Check if key exists in external_constants.
// A helper function for parse_external_constants.
bool key_exists(const char* key, NamedData* external_constants, int num_keys);

/**
* An executable method of an executorch program. Maps to a python method like
* `forward()` on the original nn.Module.
Expand All @@ -66,7 +77,9 @@ class Method final {
delegates_(rhs.delegates_),
n_chains_(rhs.n_chains_),
chains_(rhs.chains_),
init_state_(rhs.init_state_) {
init_state_(rhs.init_state_),
external_constants_(rhs.external_constants_),
num_external_constants_(rhs.num_external_constants_) {
// Required: clear out fields that the dtor looks at, so that we don't free
// anything twice.
rhs.n_value_ = 0;
Expand All @@ -84,6 +97,8 @@ class Method final {
rhs.event_tracer_ = nullptr;
rhs.n_chains_ = 0;
rhs.chains_ = nullptr;
rhs.external_constants_ = nullptr;
rhs.num_external_constants_ = 0;
}

/**
Expand Down Expand Up @@ -288,7 +303,9 @@ class Method final {
delegates_(nullptr),
n_chains_(0),
chains_(nullptr),
init_state_(InitializationState::Uninitialized) {}
init_state_(InitializationState::Uninitialized),
external_constants_(nullptr),
num_external_constants_(0) {}

/// Static factory used by Program.
ET_NODISCARD static Result<Method> load(
Expand Down Expand Up @@ -338,6 +355,23 @@ class Method final {

InitializationState init_state_;

NamedData* external_constants_;
size_t num_external_constants_ = 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

It'd be better to move these above init_state_, which has a smaller type. When possible, fields in a class should be ordered from largest to smallest, packing sub-word fields of similar sizes together to avoid holes.


/**
* Counts the number of external constants for this method.
*/
ET_NODISCARD Result<size_t> get_num_external_constants();

/**
* Parses the flatbuffer for constant tensors tagged as EXTERNAL.
* Retrieves the external constants using the named_data_map and places them
* into `external_constants_`.
* FreeableBuffers returned by the named_data_map are owned by the
* method and are freed on method destruction.
*/
ET_NODISCARD Error
parse_external_constants(const NamedDataMap* named_data_map);
/**
* Parses the elements of the values_ array. On error, n_value_ will be set to
* the number of successfully-initialized entries so that ~Method doesn't try
Expand Down
17 changes: 14 additions & 3 deletions runtime/executor/tensor_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <executorch/runtime/core/evalue.h>
#include <executorch/runtime/core/exec_aten/exec_aten.h>
#include <executorch/runtime/executor/memory_manager.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>
#include <executorch/schema/program_generated.h>

Expand All @@ -25,14 +26,21 @@ ET_NODISCARD Result<executorch::aten::Tensor> parseTensor(
const Program* program,
MemoryManager* memory_manager,
const executorch_flatbuffer::Tensor* s_tensor,
const NamedDataMap* named_data_map = nullptr);
const NamedDataMap* named_data_map = nullptr,
Span<NamedData> external_constants = {});

ET_NODISCARD Result<BoxedEvalueList<executorch::aten::Tensor>> parseTensorList(
const flatbuffers::Vector<int32_t>* tensor_indices,
EValue* values,
size_t values_len,
MemoryManager* memory_manager);

// Checks that the sizes, dim_order and scalar_type match between tensors
// stored in the PTE and externally.
ET_NODISCARD Error validateExternalTensor(
const executorch_flatbuffer::Tensor* s_tensor,
const TensorLayout& tensor_layout);

// Deserializes a List of optional type. The code here is the same between all
// list of optionals: list of optional Tensor, list of optional float etc, so we
// just use a template to avoid boilerplate.
Expand Down Expand Up @@ -105,7 +113,9 @@ parseListOptionalType(
* @param[in] nbytes The amount of memory to get from the allocator.
* @param[in] allocator The source of memory for non-constant tensors.
* @param[in] named_data_map An optional map of {name, blob} used to resolve
* data that is external to the PTE, if any.
* data that is mutable and external to the PTE, if any.
* @param[in] external_constants An optional span of {name, buffer} used to
Copy link
Contributor

Choose a reason for hiding this comment

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

Since NamedData is a generic container of a key and data, it's important to talk about the semantics of both in the context of their usage here. What do the keys represent? How should someone use them? What do the buffers contain? What guarantees exist about their contents?

* resolve data that is constant and external to the PTE, if any.
*
* @returns On success, the data pointer to use for the tensor. On failure, a
* non-Ok Error.
Expand All @@ -115,7 +125,8 @@ ET_NODISCARD Result<void*> getTensorDataPtr(
const Program* program,
size_t nbytes,
HierarchicalAllocator* allocator,
const NamedDataMap* named_data_map = nullptr);
const NamedDataMap* named_data_map = nullptr,
Span<NamedData> external_constants = {});

} // namespace deserialization
} // namespace runtime
Expand Down
7 changes: 5 additions & 2 deletions runtime/executor/tensor_parser_aten.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
#include <executorch/runtime/core/named_data_map.h>
#include <executorch/runtime/executor/memory_manager.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/executor/program.h>
#include <executorch/runtime/platform/profiler.h>
#include <executorch/schema/program_generated.h>
Expand All @@ -33,7 +34,8 @@ Result<at::Tensor> parseTensor(
const Program* program,
MemoryManager* memory_manager,
const executorch_flatbuffer::Tensor* s_tensor,
const NamedDataMap* named_data_map) {
const NamedDataMap* named_data_map,
Span<NamedData> external_constants) {
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensor");

ET_CHECK_OR_RETURN_ERROR(
Expand Down Expand Up @@ -108,7 +110,8 @@ Result<at::Tensor> parseTensor(
program,
tensor.nbytes(),
memory_manager->planned_memory(),
named_data_map);
named_data_map,
external_constants);
if (!data_ptr.ok()) {
ET_LOG(
Error,
Expand Down
Loading
Loading