Skip to content

Commit 48bb61c

Browse files
committed
[flat_tensor] Persist FreeableBuffers of external constants in method
## Problem Currently, the FlatTensorDataMap persists tensors, and returns a FreeableBuffer with an empty free function. The NamedDataMap should not persist data, as most cases (eg. delegate) will want it to be freed. Ownership should be on the caller; `get_data` returns a FreeableBuffer that 'owns' the data. The FreeableBuffer in turn is owned by the caller. NOTE: this doesn't support the case where we want to share plain tensors between methods/pte files at runtime. A custom NDM could support that use-case. ## This diff: 1. Introduces a 'NamedData' struct to method.h. This holds a key and a FreeeableBuffer. 2. Iterate over all the flatbuffer tensors to count the constants tagged with EXTERNAL. NOTE: this will increase load time for all users. Potentially allocate chunks of 16 and use a linked list to store external constants, or store this number in PTE file. 3. Allocate space for num_external_constants using the method allocator. 4. Iterate over all flatbuffer tensors and use the named_data_map to resolve EXTERNAL tensors into the array of NamedData. 5. Pass the resolved external constants to tensor_parser, along with NDM (used for mutable external tensors). 6. Resolved external tensors are stored inside method. They are freed when the method is destructed. Some notes: https://docs.google.com/document/d/1_PBi4JgODuClUPD4PCUWrKNjyUH54zOUHGUJ3QHDNes/edit?tab=t.0#heading=h.blsvwraxss7g Differential Revision: [D69477027](https://our.internmc.facebook.com/intern/diff/D69477027/) ghstack-source-id: 266173959 Pull Request resolved: #8437
1 parent 0beadcc commit 48bb61c

File tree

7 files changed

+266
-78
lines changed

7 files changed

+266
-78
lines changed

extension/flat_tensor/targets.bzl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ def define_common_targets():
1616
"//executorch/runtime/core/exec_aten:lib",
1717
"//executorch/runtime/core/exec_aten/util:tensor_util",
1818
],
19+
exported_deps = [
20+
"//executorch/extension/flat_tensor/serialize:flat_tensor_header",
21+
],
1922
visibility = [
2023
"//executorch/...",
2124
],

runtime/executor/method.cpp

Lines changed: 141 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,120 @@ Result<bool> parse_cond_value(const EValue& cond_value) {
289289

290290
} // namespace
291291

292+
Result<size_t> Method::get_num_external_constants() {
293+
auto flatbuffer_values = serialization_plan_->values();
294+
size_t n_value = flatbuffer_values->size();
295+
296+
size_t num_external_constants = 0;
297+
for (size_t i = 0; i < n_value; ++i) {
298+
auto serialization_value = flatbuffer_values->Get(i);
299+
// Ensure that the `val_as_X()` calls will return non-null pointers.
300+
ET_CHECK_OR_RETURN_ERROR(
301+
serialization_value != nullptr &&
302+
(serialization_value->val_type() ==
303+
executorch_flatbuffer::KernelTypes::Null ||
304+
serialization_value->val() != nullptr),
305+
InvalidProgram,
306+
"Null value at index %" ET_PRIsize_t,
307+
i);
308+
// Ignore non-tensor types.
309+
if (serialization_value->val_type() !=
310+
executorch_flatbuffer::KernelTypes::Tensor) {
311+
continue;
312+
}
313+
const auto s_tensor = static_cast<const executorch_flatbuffer::Tensor*>(
314+
serialization_value->val());
315+
316+
// An external constant is tagged with EXTERNAL and has no
317+
// allocation_info.
318+
if (s_tensor->extra_tensor_info() != nullptr &&
319+
s_tensor->extra_tensor_info()->location() ==
320+
executorch_flatbuffer::TensorDataLocation::EXTERNAL &&
321+
s_tensor->allocation_info() == nullptr) {
322+
num_external_constants++;
323+
}
324+
}
325+
return num_external_constants;
326+
}
327+
328+
// Check if key exists in external_constants_.
329+
// Helper function for parse_external_constants.
330+
bool key_exists(const char* key, NamedData* external_constants, int num_keys) {
331+
for (int i = 0; i < num_keys; i++) {
332+
if (strcmp(key, external_constants[i].key) == 0) {
333+
return true;
334+
}
335+
}
336+
return false;
337+
}
338+
339+
Error Method::parse_external_constants(const NamedDataMap* named_data_map) {
340+
auto flatbuffer_values = serialization_plan_->values();
341+
size_t n_value = flatbuffer_values->size();
342+
343+
// Stores the number of unique external tensors that have been resolved.
344+
int index = 0;
345+
for (size_t i = 0; i < n_value; ++i) {
346+
auto serialization_value = flatbuffer_values->Get(i);
347+
// Ignore non-tensor types.
348+
if (serialization_value->val_type() !=
349+
executorch_flatbuffer::KernelTypes::Tensor) {
350+
continue;
351+
}
352+
const auto s_tensor = static_cast<const executorch_flatbuffer::Tensor*>(
353+
serialization_value->val());
354+
// Note: tensors with allocation_info are mutable, and resolved in
355+
// parse_values.
356+
if (s_tensor->extra_tensor_info() != nullptr &&
357+
s_tensor->extra_tensor_info()->location() ==
358+
executorch_flatbuffer::TensorDataLocation::EXTERNAL &&
359+
s_tensor->allocation_info() == nullptr) {
360+
ET_CHECK_OR_RETURN_ERROR(
361+
s_tensor->extra_tensor_info()->fully_qualified_name() != nullptr,
362+
InvalidExternalData,
363+
"Fully qualified name of external tensor is null");
364+
365+
const char* key =
366+
s_tensor->extra_tensor_info()->fully_qualified_name()->c_str();
367+
368+
// Check if this tensor has already been resolved.
369+
if (!key_exists(key, external_constants_, index)) {
370+
Result<const TensorLayout> tensor_layout =
371+
named_data_map->get_metadata(key);
372+
if (!tensor_layout.ok()) {
373+
return tensor_layout.error();
374+
}
375+
// Check external tensor compatibility.
376+
Error err = deserialization::validateExternalTensor(
377+
s_tensor, tensor_layout.get());
378+
if (err != Error::Ok) {
379+
return err;
380+
}
381+
// Save the key.
382+
external_constants_[index].key = key;
383+
384+
// Save the buffer.
385+
Result<FreeableBuffer> buffer = named_data_map->get_data(key);
386+
ET_CHECK_OR_RETURN_ERROR(
387+
buffer.ok(),
388+
InvalidExternalData,
389+
"Buffer retrieved from get_data is not valid");
390+
external_constants_[index].buffer =
391+
memory_manager_->method_allocator()
392+
->allocateInstance<FreeableBuffer>();
393+
if (external_constants_[index].buffer == nullptr) {
394+
return Error::MemoryAllocationFailed;
395+
}
396+
new (external_constants_[index].buffer)
397+
FreeableBuffer(std::move(buffer.get()));
398+
399+
index++;
400+
}
401+
}
402+
}
403+
return Error::Ok;
404+
}
405+
292406
Error Method::parse_values(const NamedDataMap* named_data_map) {
293407
auto flatbuffer_values = serialization_plan_->values();
294408
ET_CHECK_OR_RETURN_ERROR(
@@ -299,23 +413,33 @@ Error Method::parse_values(const NamedDataMap* named_data_map) {
299413
return Error::MemoryAllocationFailed;
300414
}
301415

416+
// Check if there are external constants.
417+
Result<size_t> num_external_constants = get_num_external_constants();
418+
if (!num_external_constants.ok()) {
419+
return num_external_constants.error();
420+
}
421+
num_external_constants_ = *num_external_constants;
422+
if (num_external_constants_ > 0) {
423+
// Allocate space for external tensors.
424+
external_constants_ =
425+
memory_manager_->method_allocator()->allocateList<NamedData>(
426+
num_external_constants_);
427+
if (external_constants_ == nullptr) {
428+
return Error::MemoryAllocationFailed;
429+
}
430+
auto err = parse_external_constants(named_data_map);
431+
if (err != Error::Ok) {
432+
return err;
433+
}
434+
}
435+
302436
// n_value_ counts the number of successfully-initialized values for ~Method()
303437
// to clean up, and is incremented at the bottom of the loop. This makes it
304438
// safe for errors to return without updating any state.
305439
n_value_ = 0;
306440

307441
for (size_t i = 0; i < n_value; ++i) {
308442
auto serialization_value = flatbuffer_values->Get(i);
309-
// Ensure that the `val_as_X()` calls will return non-null pointers.
310-
ET_CHECK_OR_RETURN_ERROR(
311-
serialization_value != nullptr &&
312-
(serialization_value->val_type() ==
313-
executorch_flatbuffer::KernelTypes::Null ||
314-
serialization_value->val() != nullptr),
315-
InvalidProgram,
316-
"Null value at index %" ET_PRIsize_t,
317-
i);
318-
319443
const auto val = serialization_value->val();
320444

321445
switch (serialization_value->val_type()) {
@@ -416,7 +540,8 @@ Error Method::parse_values(const NamedDataMap* named_data_map) {
416540
program_,
417541
memory_manager_,
418542
static_cast<const executorch_flatbuffer::Tensor*>(val),
419-
named_data_map);
543+
named_data_map,
544+
Span<NamedData>(external_constants_, num_external_constants_));
420545
if (!t.ok()) {
421546
ET_LOG(
422547
Error,
@@ -1496,6 +1621,11 @@ Method::~Method() {
14961621
delegates_[i].~BackendDelegate();
14971622
}
14981623
}
1624+
// Free resources associated with external constants.
1625+
for (int i = 0; i < num_external_constants_; i++) {
1626+
external_constants_[i].buffer->Free();
1627+
external_constants_[i].buffer = nullptr;
1628+
}
14991629
// All other fields are trivially destructible.
15001630
}
15011631
} // namespace runtime

runtime/executor/method.h

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ using OpFunction = void (*)(KernelRuntimeContext&, EValue**);
4343
/// argument list for a single instruction
4444
using InstructionArgs = Span<EValue*>;
4545

46+
/// Data structure to hold key and data buffer for external data used
47+
/// in a method.
48+
struct NamedData {
49+
const char* key;
50+
FreeableBuffer* buffer;
51+
};
52+
4653
/**
4754
* An executable method of an executorch program. Maps to a python method like
4855
* `forward()` on the original nn.Module.
@@ -66,7 +73,9 @@ class Method final {
6673
delegates_(rhs.delegates_),
6774
n_chains_(rhs.n_chains_),
6875
chains_(rhs.chains_),
69-
init_state_(rhs.init_state_) {
76+
init_state_(rhs.init_state_),
77+
external_constants_(rhs.external_constants_),
78+
num_external_constants_(rhs.num_external_constants_) {
7079
// Required: clear out fields that the dtor looks at, so that we don't free
7180
// anything twice.
7281
rhs.n_value_ = 0;
@@ -84,6 +93,8 @@ class Method final {
8493
rhs.event_tracer_ = nullptr;
8594
rhs.n_chains_ = 0;
8695
rhs.chains_ = nullptr;
96+
rhs.external_constants_ = nullptr;
97+
rhs.num_external_constants_ = 0;
8798
}
8899

89100
/**
@@ -288,7 +299,9 @@ class Method final {
288299
delegates_(nullptr),
289300
n_chains_(0),
290301
chains_(nullptr),
291-
init_state_(InitializationState::Uninitialized) {}
302+
init_state_(InitializationState::Uninitialized),
303+
external_constants_(nullptr),
304+
num_external_constants_(0) {}
292305

293306
/// Static factory used by Program.
294307
ET_NODISCARD static Result<Method> load(
@@ -338,6 +351,23 @@ class Method final {
338351

339352
InitializationState init_state_;
340353

354+
NamedData* external_constants_;
355+
size_t num_external_constants_ = 0;
356+
357+
/**
358+
* Counts the number of external constants in flatbuffer.
359+
*/
360+
ET_NODISCARD Result<size_t> get_num_external_constants();
361+
362+
/**
363+
* Parses the flatbuffer for constant tensors tagged as EXTERNAL.
364+
* Retrieves the external constants using the named_data_map and places them
365+
* into `external_constants_`.
366+
* FreeableBuffers returned by the named_data_map are owned by the
367+
* method and are freed on method destruction.
368+
*/
369+
ET_NODISCARD Error
370+
parse_external_constants(const NamedDataMap* named_data_map);
341371
/**
342372
* Parses the elements of the values_ array. On error, n_value_ will be set to
343373
* the number of successfully-initialized entries so that ~Method doesn't try

runtime/executor/tensor_parser.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <executorch/runtime/core/evalue.h>
1515
#include <executorch/runtime/core/exec_aten/exec_aten.h>
1616
#include <executorch/runtime/executor/memory_manager.h>
17+
#include <executorch/runtime/executor/method.h>
1718
#include <executorch/runtime/executor/program.h>
1819
#include <executorch/schema/program_generated.h>
1920

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

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

38+
// Checks that the sizes, dim_order and scalar_type match between tensors
39+
// stored in the PTE and externally.
40+
ET_NODISCARD Error validateExternalTensor(
41+
const executorch_flatbuffer::Tensor* s_tensor,
42+
const TensorLayout& tensor_layout);
43+
3644
// Deserializes a List of optional type. The code here is the same between all
3745
// list of optionals: list of optional Tensor, list of optional float etc, so we
3846
// just use a template to avoid boilerplate.
@@ -105,7 +113,9 @@ parseListOptionalType(
105113
* @param[in] nbytes The amount of memory to get from the allocator.
106114
* @param[in] allocator The source of memory for non-constant tensors.
107115
* @param[in] named_data_map An optional map of {name, blob} used to resolve
108-
* data that is external to the PTE, if any.
116+
* data that is mutable and external to the PTE, if any.
117+
* @param[in] external_constants An optional span of {name, buffer} used to
118+
* resolve data that is constant and external to the PTE, if any.
109119
*
110120
* @returns On success, the data pointer to use for the tensor. On failure, a
111121
* non-Ok Error.
@@ -115,7 +125,8 @@ ET_NODISCARD Result<void*> getTensorDataPtr(
115125
const Program* program,
116126
size_t nbytes,
117127
HierarchicalAllocator* allocator,
118-
const NamedDataMap* named_data_map = nullptr);
128+
const NamedDataMap* named_data_map = nullptr,
129+
Span<NamedData> external_constants = {});
119130

120131
} // namespace deserialization
121132
} // namespace runtime

runtime/executor/tensor_parser_aten.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h>
1313
#include <executorch/runtime/core/named_data_map.h>
1414
#include <executorch/runtime/executor/memory_manager.h>
15+
#include <executorch/runtime/executor/method.h>
1516
#include <executorch/runtime/executor/program.h>
1617
#include <executorch/runtime/platform/profiler.h>
1718
#include <executorch/schema/program_generated.h>
@@ -33,7 +34,8 @@ Result<at::Tensor> parseTensor(
3334
const Program* program,
3435
MemoryManager* memory_manager,
3536
const executorch_flatbuffer::Tensor* s_tensor,
36-
const NamedDataMap* named_data_map) {
37+
const NamedDataMap* named_data_map,
38+
Span<NamedData> external_constants) {
3739
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensor");
3840

3941
ET_CHECK_OR_RETURN_ERROR(
@@ -108,7 +110,8 @@ Result<at::Tensor> parseTensor(
108110
program,
109111
tensor.nbytes(),
110112
memory_manager->planned_memory(),
111-
named_data_map);
113+
named_data_map,
114+
external_constants);
112115
if (!data_ptr.ok()) {
113116
ET_LOG(
114117
Error,

0 commit comments

Comments
 (0)