Repository navigation
Expand file tree
/
Copy pathtensor_parser_exec_aten.cpp
More file actions
381 lines (347 loc) · 13.6 KB
/
Copy pathtensor_parser_exec_aten.cpp
File metadata and controls
381 lines (347 loc) · 13.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <executorch/runtime/executor/tensor_parser.h>
#include <executorch/runtime/core/device_allocator.h>
#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/program.h>
#include <executorch/runtime/platform/profiler.h>
#include <executorch/schema/program_generated.h>
namespace executorch {
namespace ET_RUNTIME_NAMESPACE {
namespace deserialization {
using executorch::aten::ScalarType;
// Provides access to private Program methods.
class TensorParser final {
public:
ET_NODISCARD static Error load_mutable_subsegment_into(
const Program* program,
size_t mutable_data_segments_index,
size_t offset_index,
size_t size,
void* buffer) {
return program->load_mutable_subsegment_into(
mutable_data_segments_index, offset_index, size, buffer);
}
ET_NODISCARD static Result<FreeableBuffer> load_mutable_subsegment(
const Program* program,
size_t mutable_data_segments_index,
size_t offset_index,
size_t size) {
return program->load_mutable_subsegment(
mutable_data_segments_index, offset_index, size);
}
};
ET_NODISCARD Result<BoxedEvalueList<executorch::aten::Tensor>> parseTensorList(
const flatbuffers::Vector<int32_t>* tensor_indices,
EValue* values,
size_t values_len,
MemoryManager* memory_manager) {
EXECUTORCH_SCOPE_PROF("TensorParser::parseTensorList");
auto* tensor_list =
memory_manager->method_allocator()
->allocateList<executorch::aten::Tensor>(tensor_indices->size());
if (tensor_list == nullptr) {
return Error::MemoryAllocationFailed;
}
auto* evalp_list = memory_manager->method_allocator()->allocateList<EValue*>(
tensor_indices->size());
if (evalp_list == nullptr) {
return Error::MemoryAllocationFailed;
}
// For each tensor index look up the corresponding Tensor (which has been
// already allocated) and stick it in the list.
size_t output_idx = 0;
for (int32_t tensor_index : *tensor_indices) {
ET_CHECK_OR_RETURN_ERROR(
tensor_index >= 0 && static_cast<size_t>(tensor_index) < values_len,
InvalidProgram,
"Invalid value index %" PRId32 " for TensorList",
tensor_index);
auto tensor_result =
values[static_cast<size_t>(tensor_index)].tryToTensor();
if (!tensor_result.ok()) {
return tensor_result.error();
}
// Placement new as the list elements are not initialized, so calling
// copy assignment is not defined if it's non trivial.
new (&tensor_list[output_idx])
executorch::aten::Tensor(std::move(tensor_result.get()));
evalp_list[output_idx] = &values[static_cast<size_t>(tensor_index)];
output_idx++;
}
return BoxedEvalueList<executorch::aten::Tensor>(
evalp_list, tensor_list, tensor_indices->size());
}
ET_NODISCARD Error validateTensorLayout(
const executorch_flatbuffer::Tensor* s_tensor,
const TensorLayout& expected_layout) {
ET_CHECK_OR_RETURN_ERROR(
static_cast<ScalarType>(s_tensor->scalar_type()) ==
expected_layout.scalar_type(),
InvalidExternalData,
"Scalar type mismatch. Expected %hhd, got %hhd.",
static_cast<int8_t>(s_tensor->scalar_type()),
static_cast<int8_t>(expected_layout.scalar_type()));
ET_CHECK_OR_RETURN_ERROR(
s_tensor->sizes() != nullptr, InvalidExternalData, "Missing sizes field");
ET_CHECK_OR_RETURN_ERROR(
s_tensor->dim_order() != nullptr,
InvalidExternalData,
"Missing dim_order field");
int dim = s_tensor->sizes()->size();
ET_CHECK_OR_RETURN_ERROR(
dim >= 0, InvalidExternalData, "Dim is negative: %d", dim)
ET_CHECK_OR_RETURN_ERROR(
static_cast<size_t>(dim) == expected_layout.sizes().size(),
InvalidExternalData,
"Dim mismatch. Expected %d, got %zu.",
dim,
expected_layout.sizes().size());
ET_CHECK_OR_RETURN_ERROR(
s_tensor->dim_order()->size() == static_cast<size_t>(dim),
InvalidExternalData,
"Dim order size mismatch. Expected %d, got %u.",
dim,
s_tensor->dim_order()->size());
for (int i = 0; i < dim; i++) {
ET_CHECK_OR_RETURN_ERROR(
s_tensor->sizes()->Get(i) == expected_layout.sizes()[i],
InvalidExternalData,
"Sizes mismatch. Expected %d, got %d for size at index %d.",
s_tensor->sizes()->Get(i),
expected_layout.sizes()[i],
i);
ET_CHECK_OR_RETURN_ERROR(
s_tensor->dim_order()->Get(i) == expected_layout.dim_order()[i],
InvalidExternalData,
"Dim order mismatch. Expected %d, got %d for dim at index %d.",
s_tensor->dim_order()->Get(i),
expected_layout.dim_order()[i],
i);
}
return Error::Ok;
}
// Check if key exists in entries. If it does, return a pointer to the entry
// otherwise return a nullptr.
NamedData* get_data_by_key(const char* key, Span<NamedData> entries) {
for (const auto i : c10::irange(entries.size())) {
if (strcmp(key, entries[i].key) == 0) {
return &entries[i];
}
}
return nullptr;
}
// Suppress a GCC 11 false positive -Wstringop-overread triggered by
// flatbuffers' GetPointer inlining into string_view construction.
// Guarded to GCC >= 11 since the warning doesn't exist on older GCC or Clang.
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 11
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overread"
#endif
// Retrieve the buffer specified by the allocation_info
ET_NODISCARD Result<void*> getMemPlannedPtr(
const executorch_flatbuffer::AllocationDetails* allocation_info,
size_t nbytes,
HierarchicalAllocator* allocator) {
ET_CHECK_OR_RETURN_ERROR(
allocator != nullptr,
InvalidState,
"HierarchicalAllocator must not be null to resolve a planned allocation");
// TODO(T142455629): make the allocator actually id based and not indexed
// based. -1 is a hack to get the memory ids 0 aligned because previously
// 0 was reserved
const uint32_t memory_id = allocation_info->memory_id() - 1;
// Originally this field was a single uint32_t, but we need 64 bits for
// larger models. To preserve backwards compatibility, the high bits are
// managed in a separate uint32_t field.
const uint32_t memory_offset_low = allocation_info->memory_offset_low();
const uint32_t memory_offset_high = allocation_info->memory_offset_high();
size_t memory_offset = memory_offset_low;
if constexpr (sizeof(size_t) > sizeof(uint32_t)) {
memory_offset |= static_cast<size_t>(memory_offset_high) << 32;
} else {
ET_CHECK_OR_RETURN_ERROR(
memory_offset_high == 0,
NotSupported,
"size_t cannot hold memory offset 0x%08" PRIx32 "%08" PRIx32,
memory_offset_high,
memory_offset_low);
}
return allocator->get_offset_address(memory_id, memory_offset, nbytes);
}
namespace {
/// The device of the planned buffer that `allocation_info` points into. A
/// buffer without device metadata is host memory.
::executorch::runtime::etensor::Device getPlannedBufferDevice(
const executorch_flatbuffer::AllocationDetails* allocation_info,
const HierarchicalAllocator* allocator) {
const auto devices = allocator->planned_buffer_devices();
const uint32_t memory_id = allocation_info->memory_id() - 1;
if (memory_id < devices.size()) {
return devices[memory_id];
}
return ::executorch::runtime::etensor::DeviceType::CPU;
}
/// Copies `nbytes` of host `data` to `dst` in the memory of `device`. A data
/// loader writes with the CPU, so it cannot load into device memory directly.
ET_NODISCARD Error copyToDevice(
Result<FreeableBuffer> data,
void* dst,
size_t nbytes,
::executorch::runtime::etensor::Device device) {
if (!data.ok()) {
return data.error();
}
// A data loader returns no buffer for zero bytes, which allocators reject.
if (nbytes == 0) {
return Error::Ok;
}
ET_CHECK_OR_RETURN_ERROR(
data->size() >= nbytes,
InvalidProgram,
"Loaded %zu bytes for a tensor of %zu bytes",
data->size(),
nbytes);
::executorch::runtime::DeviceAllocator* device_allocator =
::executorch::runtime::get_device_allocator(device.type());
ET_CHECK_OR_RETURN_ERROR(
device_allocator != nullptr,
NotFound,
"No allocator registered for device type %d",
static_cast<int>(device.type()));
ET_CHECK_OK_OR_RETURN_ERROR(device_allocator->copy_host_to_device(
dst, data->data(), nbytes, device.index()));
// The first run may read the tensor on a stream the copy is not ordered
// with, for example when a caller sets its stream only around execute. This
// also lets `data` be freed when this returns.
return device_allocator->synchronize(device.index());
}
} // namespace
ET_NODISCARD Result<void*> getTensorDataPtr(
const executorch_flatbuffer::Tensor* s_tensor,
const Program* program,
size_t nbytes,
HierarchicalAllocator* allocator,
const NamedDataMap* named_data_map,
Span<NamedData> external_constants) {
auto data_buffer_idx = s_tensor->data_buffer_idx();
const executorch_flatbuffer::AllocationDetails* allocation_info =
s_tensor->allocation_info();
if (allocation_info != nullptr) {
ET_CHECK_OR_RETURN_ERROR(
allocator != nullptr,
InvalidState,
"HierarchicalAllocator is null but tensor has allocation_info requiring memory-planned buffers");
}
// External tensors.
if (s_tensor->extra_tensor_info() != nullptr &&
s_tensor->extra_tensor_info()->location() ==
executorch_flatbuffer::TensorDataLocation::EXTERNAL) {
// Check that fqn is not null.
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* fqn =
s_tensor->extra_tensor_info()->fully_qualified_name()->c_str();
// Constant value.
if (allocation_info == nullptr) {
NamedData* data = get_data_by_key(fqn, external_constants);
if (data != nullptr) {
return const_cast<void*>(data->buffer.data());
}
// Should never reach here; these tensors are resolved in
// Method::parse_external_constants. Any errors should be caught there.
return Error::Internal;
} else {
// Mutable value.
// Look up tensor in named data map.
ET_CHECK_OR_RETURN_ERROR(
named_data_map != nullptr,
InvalidExternalData,
"Cannot retrieve external tensor with fqn: %s. The named_data_map is null; most likely no external .ptd file was provided.",
fqn);
Result<const TensorLayout> tensor_layout_res =
named_data_map->get_tensor_layout(fqn);
if (!tensor_layout_res.ok()) {
return tensor_layout_res.error();
}
const TensorLayout& tensor_layout = tensor_layout_res.get();
Error err = validateTensorLayout(s_tensor, tensor_layout);
if (err != Error::Ok) {
return err;
}
// Call load_into.
auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator);
if (!planned_ptr.ok()) {
return planned_ptr.error();
}
const auto device = getPlannedBufferDevice(allocation_info, allocator);
auto load_error = device.is_cpu()
? named_data_map->load_data_into(fqn, planned_ptr.get(), nbytes)
: copyToDevice(
named_data_map->get_data(fqn),
planned_ptr.get(),
nbytes,
device);
if (load_error != Error::Ok) {
return load_error;
}
return planned_ptr;
}
// Constant, stored in PTE file.
} else if (data_buffer_idx > 0 && allocation_info == nullptr) {
auto const_data =
program->get_constant_buffer_data(data_buffer_idx, nbytes);
if (!const_data.ok()) {
return const_data.error();
}
// The const_cast is 'ok' here because the program and runtime should
// guarantee that this data is never modified.
return const_cast<void*>(const_data.get());
// Memory Planned, with initial state
} else if (data_buffer_idx > 0 && allocation_info != nullptr) {
auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator);
if (!planned_ptr.ok()) {
return planned_ptr.error();
}
// A program whose initial data is all empty has no mutable segment to
// load an empty tensor from.
if (nbytes == 0) {
return planned_ptr;
}
const auto device = getPlannedBufferDevice(allocation_info, allocator);
auto err = device.is_cpu()
? TensorParser::load_mutable_subsegment_into(
program, 0, data_buffer_idx, nbytes, planned_ptr.get())
: copyToDevice(
TensorParser::load_mutable_subsegment(
program, 0, data_buffer_idx, nbytes),
planned_ptr.get(),
nbytes,
device);
if (err != Error::Ok) {
return err;
}
return planned_ptr;
// Memory planned, no initial state
} else if (data_buffer_idx == 0 && allocation_info != nullptr) {
return getMemPlannedPtr(allocation_info, nbytes, allocator);
// Pointer recived at runtime
} else { // data_buffer_idx == 0 && allocation_info == nullptr,
return nullptr;
}
}
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ >= 11
#pragma GCC diagnostic pop
#endif
} // namespace deserialization
} // namespace ET_RUNTIME_NAMESPACE
} // namespace executorch