Skip to content

[flat_tensor] Persist FreeableBuffers of external constants in method #8599

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

Merged
merged 1 commit into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
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