Skip to content

Commit 6944c45

Browse files
dbortfacebook-github-bot
authored andcommitted
Clean up MemoryManager API (#402)
Summary: Pull Request resolved: #402 - Remove const allocator, which we don't use - Don't make users create empty allocators for entries they don't use - Pick more-descriptive names ghstack-source-id: 201207625 exported-using-ghexport Reviewed By: iseeyuan Differential Revision: D49389211 fbshipit-source-id: 3e2be724e708a2ebafeffac2a4cfc33d0f2ebcfe
1 parent 9eca2a9 commit 6944c45

13 files changed

+235
-137
lines changed

runtime/executor/memory_manager.h

Lines changed: 71 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -14,88 +14,95 @@
1414
namespace torch {
1515
namespace executor {
1616

17-
// The memory manager for the executor. It is responsible for keeping
18-
// track of the memory allocation across the lifespan of the executor,
19-
// providing allocators for the following types of objects:
20-
//
21-
// 1. Constants - Constant values in the program. TODO(myuan): we may not
22-
// need it (because the constants are hardcoded in the flatbuffer) but
23-
// we'll account for it for the time being for completeness.
24-
//
25-
// 2. Non-constants - Non-constant values in the program, which may or may not
26-
// be tied to a memory plan.
27-
//
28-
// 3. Runtime structures - Any data needed by the executor itself.
29-
// TODO(myuan): determine whether Delegates need to receive it in the "init"
30-
// method for backends to use directly, or whether memory needs will be
31-
// expressed as an argument to the delegated methods for memory planning to
32-
// account for. Same concerns about dynamic behaviour apply.
33-
34-
// 4. Kernel temporary - This is to provide kernels with a way to create memory,
35-
// without having to request it by adding an extra argument. The extra argument
36-
// approach is fine if/when planning desires to account for such memory, but in
37-
// certain cases a kernel may be fine just leaving this as an implementation
38-
// detail of the kernels itself (but we still want to be able to capture such
39-
// memory allocation).
40-
41-
// In general, this memory manager aims to consolidate all dynamic memory needs
42-
// for program execution. This can allow for heap-less execution (relevant to
43-
// some embedded scenarios), and overall have a tighter control over memory
44-
// utilization. The manager, however, cannot ensure all allocation is accounted
45-
// for since kernel implementations are free to use a separate way to allocate
46-
// memory (e.g. for things like scratch space).
47-
// TODO(myuan): analyze the stack data overhead and lifespan.
48-
49-
class MemoryManager {
17+
/**
18+
* A container class for allocators used during Method load and execution.
19+
*
20+
* This class consolidates all dynamic memory needs for Method load and
21+
* execution. This can allow for heap-based as well as heap-less execution
22+
* (relevant to some embedded scenarios), and overall provides more control over
23+
* memory use.
24+
*
25+
* This class, however, cannot ensure all allocation is accounted for since
26+
* kernel and backend implementations are free to use a separate way to allocate
27+
* memory (e.g., for things like scratch space). But we do suggest that backends
28+
* and kernels use these provided allocators whenever possible.
29+
*/
30+
class MemoryManager final {
5031
public:
51-
MemoryManager(
52-
MemoryAllocator* constant_allocator,
53-
HierarchicalAllocator* non_constant_allocator,
54-
MemoryAllocator* runtime_allocator,
55-
MemoryAllocator* kernel_temporary_allocator)
56-
: constant_allocator_(constant_allocator),
57-
non_constant_allocator_(non_constant_allocator),
58-
runtime_allocator_(runtime_allocator),
59-
kernel_temporary_allocator_(kernel_temporary_allocator) {}
32+
/**
33+
* Constructs a new MemoryManager.
34+
*
35+
* @param[in] method_allocator The allocator to use when loading a Method and
36+
* allocating its internal structures. Must outlive the Method that uses
37+
* it.
38+
* @param[in] planned_memory The memory-planned buffers to use for mutable
39+
* tensor data when executing a Method. Must outlive the Method that uses
40+
* it. May be `nullptr` if the Method does not use any memory-planned
41+
* tensor data. The sizes of the buffers in this HierarchicalAllocator
42+
* must agree with the corresponding
43+
* `MethodMeta::num_memory_planned_buffers()` and
44+
* `MethodMeta::memory_planned_buffer_size(N)` values, which are embedded
45+
* in the Program.
46+
* @param[in] temp_allocator The allocator to use when allocating temporary
47+
* data during kernel or delegate execution. Must outlive the Method that
48+
* uses it. May be `nullptr` if the Method does not use kernels or
49+
* delegates that allocate temporary data. This allocator will be reset
50+
* after every kernel or delegate call during execution.
51+
*/
52+
explicit MemoryManager(
53+
MemoryAllocator* method_allocator,
54+
HierarchicalAllocator* planned_memory = nullptr,
55+
MemoryAllocator* temp_allocator = nullptr)
56+
: method_allocator_(method_allocator),
57+
planned_memory_(planned_memory),
58+
temp_allocator_(temp_allocator) {}
6059

6160
/**
62-
* Returns an allocator for constant values in the program.
61+
* DEPRECATED: Use the constructor without `constant_allocator` instead.
62+
*
63+
* TODO(T162089316): Remove this once all users migrate to the new ctor.
6364
*/
64-
const MemoryAllocator* get_constant_allocator() const {
65-
return constant_allocator_;
66-
}
65+
__ET_DEPRECATED MemoryManager(
66+
__ET_UNUSED MemoryAllocator* constant_allocator,
67+
HierarchicalAllocator* non_constant_allocator,
68+
MemoryAllocator* runtime_allocator,
69+
MemoryAllocator* kernel_temporary_allocator)
70+
: MemoryManager(
71+
/*method_allocator=*/runtime_allocator,
72+
/*planned_memory=*/non_constant_allocator,
73+
/*temp_allocator=*/kernel_temporary_allocator) {}
6774

6875
/**
69-
* Returns an hierarchical allocator for non-constant values in the program.
76+
* Returns the allocator that the runtime will use to allocate internal
77+
* structures while loading a Method. Must not be used after its associated
78+
* Method has been loaded.
7079
*/
71-
HierarchicalAllocator* get_non_constant_allocator() const {
72-
return non_constant_allocator_;
80+
MemoryAllocator* method_allocator() const {
81+
return method_allocator_;
7382
}
7483

7584
/**
76-
* Returns an allocator to be used for any runtime internal structures
77-
* (i.e. not directly program values).
85+
* Returns the memory-planned buffers to use for mutable tensor data.
7886
*/
79-
MemoryAllocator* get_runtime_allocator() const {
80-
return runtime_allocator_;
87+
HierarchicalAllocator* planned_memory() const {
88+
return planned_memory_;
8189
}
8290

8391
/**
84-
* Returns an allocator that kernel implementations can use to
85-
* create temporary memory (i.e. whose lifespan is a single execution
86-
* of the kernel).
92+
* Returns the allocator to use to allocate temporary data during kernel or
93+
* delegate execution.
94+
*
95+
* This allocator will be reset after every kernel or delegate call during
96+
* execution.
8797
*/
88-
MemoryAllocator* get_kernel_temporary_allocator() const {
89-
return kernel_temporary_allocator_;
98+
MemoryAllocator* temp_allocator() const {
99+
return temp_allocator_;
90100
}
91101

92-
virtual ~MemoryManager() {}
93-
94102
private:
95-
const MemoryAllocator* constant_allocator_;
96-
HierarchicalAllocator* non_constant_allocator_;
97-
MemoryAllocator* runtime_allocator_;
98-
MemoryAllocator* kernel_temporary_allocator_;
103+
MemoryAllocator* method_allocator_;
104+
HierarchicalAllocator* planned_memory_;
105+
MemoryAllocator* temp_allocator_;
99106
};
100107

101108
} // namespace executor

runtime/executor/method.cpp

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class BackendDelegate final {
4444
*
4545
* @param[in] delegate The serialized backend delegate to load.
4646
* @param[in] program The serialized program to load from.
47-
* @param[in] runtime_allocator Allocator for creating runtime C++ objects.
47+
* @param[in] backend_init_context The context pointer to pass to the
48+
* backend's init() method.
4849
* @param[out] out The BackendDelegate to initialize.
4950
*
5051
* @returns Error::Ok if the initialization succeeded, or an error otherwise.
@@ -212,12 +213,12 @@ struct Chain {
212213
namespace {
213214

214215
Result<InstructionArgs> gen_instruction_arguments(
215-
MemoryAllocator* runtime_allocator,
216+
MemoryAllocator* method_allocator,
216217
EValue* values,
217218
size_t num_args,
218219
const int32_t* arg_idxs) {
219220
EValue** arg_list =
220-
ET_ALLOCATE_LIST_OR_RETURN_ERROR(runtime_allocator, EValue*, num_args);
221+
ET_ALLOCATE_LIST_OR_RETURN_ERROR(method_allocator, EValue*, num_args);
221222
for (size_t i = 0; i < num_args; ++i) {
222223
arg_list[i] = &values[arg_idxs[i]];
223224
}
@@ -267,7 +268,7 @@ Error Method::parse_values() {
267268
ET_CHECK(flatbuffer_values != nullptr);
268269
size_t n_value = flatbuffer_values->size();
269270
values_ = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
270-
memory_manager_->get_runtime_allocator(), EValue, n_value);
271+
memory_manager_->method_allocator(), EValue, n_value);
271272

272273
// n_value_ counts the number of successfully-initialized values for ~Method()
273274
// to clean up, and is incremented at the bottom of the loop. This makes it
@@ -299,10 +300,10 @@ Error Method::parse_values() {
299300
// Allocate space for boxed and unboxed list representations using
300301
// values_ as source of truth
301302
auto* evalp_list =
302-
memory_manager_->get_runtime_allocator()->allocateList<EValue*>(
303+
memory_manager_->method_allocator()->allocateList<EValue*>(
303304
items->size());
304305
auto* int_list =
305-
memory_manager_->get_runtime_allocator()->allocateList<int64_t>(
306+
memory_manager_->method_allocator()->allocateList<int64_t>(
306307
items->size());
307308

308309
// initialize boxed list
@@ -452,9 +453,9 @@ Error Method::resolve_operator(
452453
populateOperatorName(op, kTempBufferSizeForName, operator_name);
453454

454455
// resolve tensor meta
455-
auto runtime_allocator = memory_manager_->get_runtime_allocator();
456+
auto method_allocator = memory_manager_->method_allocator();
456457
TensorMeta* meta =
457-
ET_ALLOCATE_LIST_OR_RETURN_ERROR(runtime_allocator, TensorMeta, n_args);
458+
ET_ALLOCATE_LIST_OR_RETURN_ERROR(method_allocator, TensorMeta, n_args);
458459
size_t count = 0;
459460
for (size_t i = 0; i < n_args; i++) {
460461
EValue* eval = args[i];
@@ -463,7 +464,7 @@ Error Method::resolve_operator(
463464
auto tensor = eval->toTensor();
464465
meta[count].dtype_ = tensor.scalar_type();
465466
exec_aten::DimOrderType* dim_order_ptr = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
466-
runtime_allocator, exec_aten::DimOrderType, tensor.dim());
467+
method_allocator, exec_aten::DimOrderType, tensor.dim());
467468
size_t size = tensor.dim();
468469
Error err = get_dim_order(tensor, dim_order_ptr, size);
469470
ET_CHECK_OR_RETURN_ERROR(
@@ -514,7 +515,7 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
514515
init_state_ =
515516
InitializationState::InitializationFailed; // Until proven otherwise
516517
serialization_plan_ = s_plan;
517-
auto runtime_allocator = memory_manager_->get_runtime_allocator();
518+
auto method_allocator = memory_manager_->method_allocator();
518519

519520
{
520521
// Parse the elements of the values_ array.
@@ -530,7 +531,7 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
530531
ET_CHECK(delegates != nullptr);
531532
size_t n_delegate = delegates->size();
532533
delegates_ = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
533-
runtime_allocator, BackendDelegate, n_delegate);
534+
method_allocator, BackendDelegate, n_delegate);
534535

535536
// n_delegate_ counts the number of successfully-initialized delegates for
536537
// ~Method() to clean up, and is incremented at the bottom of the loop. This
@@ -539,7 +540,7 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
539540

540541
for (size_t i = 0; i < n_delegate; ++i) {
541542
const auto& delegate = *delegates->Get(i);
542-
BackendInitContext backend_init_context(runtime_allocator);
543+
BackendInitContext backend_init_context(method_allocator);
543544
Error err = BackendDelegate::Init(
544545
delegate, program_, backend_init_context, &delegates_[i]);
545546
if (err != Error::Ok) {
@@ -559,15 +560,15 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
559560
n_chains_ = chains->size();
560561

561562
chains_ =
562-
ET_ALLOCATE_LIST_OR_RETURN_ERROR(runtime_allocator, Chain, n_chains_);
563+
ET_ALLOCATE_LIST_OR_RETURN_ERROR(method_allocator, Chain, n_chains_);
563564
int32_t num_instructions_missing_op = 0;
564565
for (size_t i = 0; i < n_chains_; ++i) {
565566
auto s_chain = chains->Get(i);
566567
auto num_instructions = s_chain->instructions()->size();
567568
auto chain_instruction_kernels = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
568-
runtime_allocator, OpFunction, num_instructions);
569+
method_allocator, OpFunction, num_instructions);
569570
auto chain_instruction_arg_lists = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
570-
runtime_allocator, InstructionArgs, num_instructions);
571+
method_allocator, InstructionArgs, num_instructions);
571572

572573
// Set up the argument lists ahead of time and store pointers to them to
573574
// use when the instructions are called
@@ -579,7 +580,7 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
579580
const auto arg_idxs =
580581
instruction->instr_args_as_KernelCall()->args();
581582
auto res = gen_instruction_arguments(
582-
runtime_allocator, values_, arg_idxs->size(), arg_idxs->data());
583+
method_allocator, values_, arg_idxs->size(), arg_idxs->data());
583584
if (!res.ok()) {
584585
return res.error();
585586
}
@@ -600,7 +601,7 @@ Error Method::init(executorch_flatbuffer::ExecutionPlan* s_plan) {
600601
const auto arg_idxs =
601602
instruction->instr_args_as_DelegateCall()->args();
602603
auto res = gen_instruction_arguments(
603-
runtime_allocator, values_, arg_idxs->size(), arg_idxs->data());
604+
method_allocator, values_, arg_idxs->size(), arg_idxs->data());
604605
if (!res.ok()) {
605606
return res.error();
606607
}

runtime/executor/method_meta.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,14 @@ Result<TensorInfo> MethodMeta::output_tensor_meta(size_t index) const {
169169
static_cast<exec_aten::ScalarType>(tensor_value->scalar_type()));
170170
}
171171

172-
size_t MethodMeta::num_non_const_buffers() const {
172+
size_t MethodMeta::num_memory_planned_buffers() const {
173173
// Index zero is reserved internally, and we hide it from users. The actual
174174
// number of buffers is one fewer than the actual size of this list in the
175175
// program.
176176
return s_plan_->non_const_buffer_sizes()->size() - 1;
177177
}
178178

179-
Result<int64_t> MethodMeta::non_const_buffer_size(size_t index) const {
179+
Result<int64_t> MethodMeta::memory_planned_buffer_size(size_t index) const {
180180
auto num_buffers = this->num_non_const_buffers();
181181
ET_CHECK_OR_RETURN_ERROR(
182182
index >= 0 && index < num_buffers,

runtime/executor/method_meta.h

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -162,19 +162,33 @@ class MethodMeta final {
162162
Result<TensorInfo> output_tensor_meta(size_t index) const;
163163

164164
/**
165-
* Get the number of non-constant buffers this method requires.
165+
* Get the number of memory-planned buffers this method requires.
166166
*
167-
* @returns The number of non-constant buffers.
167+
* @returns The number of memory-planned buffers.
168168
*/
169-
size_t num_non_const_buffers() const;
169+
size_t num_memory_planned_buffers() const;
170170

171171
/**
172-
* Get the size in bytes of the specified non-constant buffer.
172+
* Get the size in bytes of the specified memory-planned buffer.
173173
*
174174
* @param[in] index The index of the buffer to look up.
175175
* @returns The size in bytes on success, or an error on failure.
176176
*/
177-
Result<int64_t> non_const_buffer_size(size_t index) const;
177+
Result<int64_t> memory_planned_buffer_size(size_t index) const;
178+
179+
/**
180+
* DEPRECATED: Use num_memory_planned_buffers() instead.
181+
*/
182+
__ET_DEPRECATED size_t num_non_const_buffers() const {
183+
return num_memory_planned_buffers();
184+
}
185+
186+
/**
187+
* DEPRECATED: Use memory_planned_buffer_size() instead.
188+
*/
189+
Result<int64_t> non_const_buffer_size(size_t index) const {
190+
return memory_planned_buffer_size(index);
191+
}
178192

179193
private:
180194
// Let Program create MethodMeta.

runtime/executor/tensor_parser.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ parseListOptionalType(
3838
EValue* values_,
3939
MemoryManager* memory_manager) {
4040
auto* evalp_list = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
41-
memory_manager->get_runtime_allocator(), EValue*, value_indices->size());
41+
memory_manager->method_allocator(), EValue*, value_indices->size());
4242

4343
auto* optional_tensor_list = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
44-
memory_manager->get_runtime_allocator(),
44+
memory_manager->method_allocator(),
4545
exec_aten::optional<T>,
4646
value_indices->size());
4747

runtime/executor/tensor_parser_aten.cpp

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,7 @@ Result<at::Tensor> parseTensor(
8484
} else {
8585
// Now that we know how big the tensor is, find and assign its memory.
8686
Result<void*> data_ptr = getTensorDataPtr(
87-
s_tensor,
88-
program,
89-
tensor.nbytes(),
90-
memory_manager->get_non_constant_allocator());
87+
s_tensor, program, tensor.nbytes(), memory_manager->planned_memory());
9188
if (!data_ptr.ok()) {
9289
ET_LOG(Error, "getTensorDataPtr() failed: 0x%" PRIx32, data_ptr.error());
9390
return data_ptr.error();

runtime/executor/tensor_parser_exec_aten.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ __ET_NODISCARD Result<BoxedEvalueList<exec_aten::Tensor>> parseTensorList(
2727
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensorList");
2828

2929
auto* tensor_list = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
30-
memory_manager->get_runtime_allocator(),
30+
memory_manager->method_allocator(),
3131
exec_aten::Tensor,
3232
tensor_indices->size());
3333
auto* evalp_list = ET_ALLOCATE_LIST_OR_RETURN_ERROR(
34-
memory_manager->get_runtime_allocator(), EValue*, tensor_indices->size());
34+
memory_manager->method_allocator(), EValue*, tensor_indices->size());
3535

3636
// For each tensor index look up the corresponding Tensor (which has been
3737
// already allocated) and stick it in the list.

0 commit comments

Comments
 (0)