Skip to content
149 changes: 138 additions & 11 deletions runtime/executor/method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
namespace executorch {
namespace runtime {

using deserialization::NamedData;
using internal::PlatformMemoryAllocator;

/**
Expand Down Expand Up @@ -289,6 +290,113 @@ 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 n_external_constants = 0;
for (size_t i = 0; i < n_value; ++i) {
auto serialization_value = flatbuffer_values->Get(i);
// Ensure values are non-null.
// 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) {
n_external_constants++;
}
}
return n_external_constants;
}

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

// n_external_constants_ counts the number of successfully-initialized
// external constants 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_external_constants_ = 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) {
continue;
}
ET_CHECK_OR_RETURN_ERROR(
s_tensor->extra_tensor_info()->fully_qualified_name() != nullptr,
InvalidExternalData,
"Fully qualified name of external tensor is null at index %zu",
i);

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

// Check if this tensor has already been resolved.
if (get_data_by_key(
key, Span<NamedData>(external_constants_, n_external_constants_)) !=
nullptr) {
continue;
}
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::validateTensorLayout(s_tensor, tensor_layout.get());
if (err != Error::Ok) {
return err;
}
// Save the key.
external_constants_[n_external_constants_].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");
new (&external_constants_[n_external_constants_].buffer)
FreeableBuffer(std::move(buffer.get()));

n_external_constants_ += 1;
}
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 +407,37 @@ Error Method::parse_values(const NamedDataMap* named_data_map) {
return Error::MemoryAllocationFailed;
}

// Count the number of tensors marked as EXTERNAL for this method. The actual
// number of external constants may be smaller, eg. if multiple tensors point
// to the same underlying data buffer.
// This function also ensures that all flatbuffer_values entries
// are non-null, so `val_as_X()` calls below are guaranteed to return
// non-null pointers.
Result<size_t> max_external_constants = get_num_external_constants();
if (!max_external_constants.ok()) {
return max_external_constants.error();
}
if (max_external_constants.get() > 0) {
// Allocate space for external tensors.
external_constants_ =
memory_manager_->method_allocator()->allocateList<NamedData>(
max_external_constants.get());
if (external_constants_ == nullptr) {
return Error::MemoryAllocationFailed;
}
Error 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_, n_external_constants_));
if (!t.ok()) {
ET_LOG(
Error,
Expand Down Expand Up @@ -1496,6 +1619,10 @@ Method::~Method() {
delegates_[i].~BackendDelegate();
}
}
// Free resources associated with external constants.
for (int i = 0; i < n_external_constants_; i++) {
external_constants_[i].buffer.~FreeableBuffer();
}
// All other fields are trivially destructible.
}
} // namespace runtime
Expand Down
36 changes: 36 additions & 0 deletions runtime/executor/method.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ struct EValue;
namespace executorch {
namespace runtime {

// Forward declare NamedData. This is a public header and must not include
// internal data types.
namespace deserialization {
struct NamedData;
} // namespace deserialization

// Forward declare Program to avoid a circular reference.
class Program;

Expand All @@ -42,6 +48,7 @@ using OpFunction = void (*)(KernelRuntimeContext&, EValue**);
/// A list of pointers into the master values table that together compose the
/// argument list for a single instruction
using InstructionArgs = Span<EValue*>;
using deserialization::NamedData;

/**
* An executable method of an executorch program. Maps to a python method like
Expand All @@ -66,13 +73,17 @@ class Method final {
delegates_(rhs.delegates_),
n_chains_(rhs.n_chains_),
chains_(rhs.chains_),
external_constants_(rhs.external_constants_),
n_external_constants_(rhs.n_external_constants_),
init_state_(rhs.init_state_) {
// Required: clear out fields that the dtor looks at, so that we don't free
// anything twice.
rhs.n_value_ = 0;
rhs.values_ = nullptr;
rhs.n_delegate_ = 0;
rhs.delegates_ = nullptr;
rhs.n_external_constants_ = 0;
rhs.external_constants_ = nullptr;

// Helpful: Try to ensure that any other interactions with the old object
// result in failures.
Expand Down Expand Up @@ -288,6 +299,8 @@ class Method final {
delegates_(nullptr),
n_chains_(0),
chains_(nullptr),
external_constants_(nullptr),
n_external_constants_(0),
init_state_(InitializationState::Uninitialized) {}

/// Static factory used by Program.
Expand Down Expand Up @@ -336,8 +349,31 @@ class Method final {
size_t n_chains_;
Chain* chains_;

NamedData* external_constants_;
size_t n_external_constants_ = 0;

InitializationState init_state_;

/**
* Counts the number of tensors marked as EXTERNAL in the flatbuffer
* 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_`. Updates `n_external_constants_` to count the
* number of successfully-initialized external constants.
* FreeableBuffers returned by the named_data_map are owned by the
* method and are freed on method destruction.
*
* @param[in] named_data_map, to retrieve external constants from.
* @returns Error::Ok on success, non-Ok on failure.
*/
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
27 changes: 24 additions & 3 deletions runtime/executor/tensor_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,34 @@ namespace executorch {
namespace runtime {
namespace deserialization {

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

NamedData* get_data_by_key(const char* key, Span<NamedData> entries);

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 validateTensorLayout(
const executorch_flatbuffer::Tensor* s_tensor,
const TensorLayout& expected_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 +121,11 @@ 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 containing tensor fqn to
* corresponding tensor data. Used to resolve data that is constant and
* external to the PTE, if any. Referencing data from external_constants is
* safe, as it has the same lifetime as the method.
*
* @returns On success, the data pointer to use for the tensor. On failure, a
* non-Ok Error.
Expand All @@ -115,7 +135,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
6 changes: 4 additions & 2 deletions runtime/executor/tensor_parser_aten.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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 +109,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