-
Notifications
You must be signed in to change notification settings - Fork 528
/
Copy pathbundled_program.cpp
437 lines (387 loc) · 14.5 KB
/
bundled_program.cpp
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
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
/*
* 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/devtools/bundled_program/bundled_program.h>
#include <cmath>
#include <cstddef>
#include <cstring>
#ifdef USE_ATEN_LIB
#include <ATen/ATen.h>
#endif // USE_ATEN_LIB
#include <executorch/devtools/bundled_program/schema/bundled_program_schema_generated.h>
#include <executorch/runtime/core/event_tracer_hooks.h>
#include <executorch/runtime/core/exec_aten/util/dim_order_util.h>
#include <executorch/runtime/core/memory_allocator.h>
#include <executorch/runtime/executor/method.h>
#include <executorch/runtime/platform/log.h>
using executorch::aten::ArrayRef;
using executorch::aten::Half;
using executorch::aten::ScalarType;
using executorch::aten::Tensor;
using ::executorch::ET_RUNTIME_NAMESPACE::Method;
using ::executorch::runtime::Error;
using ::executorch::runtime::EValue;
using ::executorch::runtime::Result;
namespace executorch {
namespace BUNDLED_PROGRAM_NAMESPACE {
namespace {
constexpr size_t kMaxDim = 16;
#ifdef USE_ATEN_LIB
// Create an aten tensor with same content using bundled tensor
at::Tensor tensor_like(bundled_program_flatbuffer::Tensor* bundled_tensor) {
ET_CHECK(bundled_tensor->sizes()->size() <= kMaxDim);
int64_t ret_t_sizes[kMaxDim];
for (size_t i = 0; i < bundled_tensor->sizes()->size(); i++) {
ret_t_sizes[i] = static_cast<int64_t>(bundled_tensor->sizes()->data()[i]);
}
at::Tensor ret_tensor = at::zeros(
{ret_t_sizes, bundled_tensor->sizes()->size()},
at::dtype(static_cast<ScalarType>(bundled_tensor->scalar_type())));
memcpy(
ret_tensor.mutable_data_ptr(),
static_cast<const void*>(bundled_tensor->data()->Data()),
ret_tensor.nbytes());
return ret_tensor;
}
#else // !USE_ATEN_LIB
using torch::executor::TensorImpl;
// Create a tensorimpl with same content using bundled tensor
TensorImpl impl_like(bundled_program_flatbuffer::Tensor* bundled_tensor) {
ScalarType scalar_type =
static_cast<ScalarType>(bundled_tensor->scalar_type());
ssize_t dim = bundled_tensor->sizes()->size();
executorch::aten::SizesType* sizes = bundled_tensor->mutable_sizes()->data();
void* data = bundled_tensor->mutable_data()->data();
executorch::aten::DimOrderType* dim_order =
bundled_tensor->mutable_dim_order()->data();
// The strides of created tensorimpl will only be actually used when
// comparsion (`tensor_are_close` below). To eliminate the usage of memory
// allocator, here we set the initial strides as null and reconstruct the
// stride array as temporary varible when comparsion.
executorch::aten::StridesType* strides = nullptr;
return TensorImpl(scalar_type, dim, sizes, data, dim_order, strides);
}
#endif
/**
* Returns true if the two elements are close according to the description on
* `tensors_are_close()`.
*
* T must be a floating point type. Non-floating point data should be compared
* directly.
*/
template <
typename T,
typename = std::enable_if_t<std::is_floating_point<T>::value>>
bool elem_is_close(const T& ai, const T& bi, double rtol, double atol) {
if (std::isnan(ai) && std::isnan(bi)) {
// NaN == NaN
} else if (
!std::isfinite(ai) && !std::isfinite(bi) && ((ai > 0) == (bi > 0))) {
// -Inf == -Inf
// +Inf == +Inf
} else if (rtol == 0 && atol == 0) {
// Exact comparison; avoid unnecessary math.
if (ai != bi) {
return false;
}
} else {
auto allowed_error = atol + std::abs(rtol * bi);
auto actual_error = std::abs(ai - bi);
if (!std::isfinite(actual_error) || actual_error > allowed_error) {
return false;
}
}
return true;
}
template <
typename T,
typename = std::enable_if_t<std::is_floating_point<T>::value>>
bool data_is_close(
const T* a,
const T* b,
size_t numel,
double rtol,
double atol) {
for (size_t i = 0; i < numel; i++) {
if (!elem_is_close(a[i], b[i], rtol, atol)) {
return false;
}
}
return true;
}
bool data_is_close_half(
const Half* a,
const Half* b,
size_t numel,
double rtol,
double atol) {
for (size_t i = 0; i < numel; i++) {
if (!elem_is_close(
static_cast<double>(a[i]), static_cast<double>(b[i]), rtol, atol)) {
return false;
}
}
return true;
}
bool tensors_are_close(
const Tensor& bundled_tensor,
const Tensor& method_output_tensor,
double rtol,
double atol) {
if (bundled_tensor.scalar_type() != method_output_tensor.scalar_type() ||
bundled_tensor.sizes() != method_output_tensor.sizes()) {
return false;
}
#ifdef USE_ATEN_LIB
ET_CHECK_MSG(
bundled_tensor.strides() == method_output_tensor.strides(),
"The two inputs of `tensors_are_close` function shall have same strides");
#else // !USE_ATEN_LIB
// Contruct stride array for bundled tensor based on its dim order since
// strides of bundled_tensor in lean mode is null.
executorch::aten::StridesType strides[kMaxDim] = {0};
auto status = torch::executor::dim_order_to_stride(
bundled_tensor.sizes().data(),
bundled_tensor.dim_order().data(),
bundled_tensor.dim(),
strides);
ET_CHECK_MSG(
status == Error::Ok, "dim_order_to_stride returned invalid status");
// TODO(T132992348): support comparison between tensors of different strides
ET_CHECK_MSG(
ArrayRef<executorch::aten::StridesType>(strides, bundled_tensor.dim()) ==
method_output_tensor.strides(),
"The two inputs of `tensors_are_close` function shall have same strides");
#endif
// Since the two tensors have same shape and strides, any two elements that
// share same index from underlying data perspective will also share same
// index from tensor perspective, whatever the size and strides really are.
// e.g. if a[i_1, i_2, ... i_n] = a.const_data_ptr()[m], we can assert
// b[i_1, i_2, ... i_n] = b.const_data_ptr()[m])
// So we can just compare the two underlying data sequentially to figure out
// if the two tensors are same.
if (bundled_tensor.nbytes() == 0) {
// Note that this case is important. It's valid for a zero-size tensor to
// have a null data pointer, but in some environments it's invalid to pass a
// null pointer to memcmp() even when the size is zero.
return true;
} else if (bundled_tensor.scalar_type() == ScalarType::Float) {
return data_is_close<float>(
bundled_tensor.const_data_ptr<float>(),
method_output_tensor.const_data_ptr<float>(),
bundled_tensor.numel(),
rtol,
atol);
} else if (bundled_tensor.scalar_type() == ScalarType::Double) {
return data_is_close<double>(
bundled_tensor.const_data_ptr<double>(),
method_output_tensor.const_data_ptr<double>(),
bundled_tensor.numel(),
rtol,
atol);
} else if (bundled_tensor.scalar_type() == ScalarType::Half) {
return data_is_close_half(
bundled_tensor.const_data_ptr<Half>(),
method_output_tensor.const_data_ptr<Half>(),
bundled_tensor.numel(),
rtol,
atol);
} else {
// Non-floating-point types can be compared bitwise.
return memcmp(
bundled_tensor.const_data_ptr(),
method_output_tensor.const_data_ptr(),
bundled_tensor.nbytes()) == 0;
}
}
Result<bundled_program_flatbuffer::BundledMethodTestSuite*>
get_method_test_suite(
const bundled_program_flatbuffer::BundledProgram* bundled_program,
Method& method) {
const char* method_name = method.method_meta().name();
auto method_test_suites = bundled_program->method_test_suites();
for (size_t i = 0; i < method_test_suites->size(); i++) {
auto m_test = method_test_suites->GetMutableObject(i);
if (std::strcmp(m_test->method_name()->c_str(), method_name) == 0) {
return m_test;
}
}
ET_LOG(Error, "No method named '%s' in given bundled program", method_name);
return Error::InvalidArgument;
}
} // namespace
// Load testset_idx-th bundled data into the Method
ET_NODISCARD Error load_bundled_input(
Method& method,
SerializedBundledProgram* bundled_program_ptr,
size_t testset_idx) {
ET_CHECK_OR_RETURN_ERROR(
bundled_program_flatbuffer::BundledProgramBufferHasIdentifier(
bundled_program_ptr),
NotSupported,
"The input buffer should be a bundled program.");
auto method_test = get_method_test_suite(
bundled_program_flatbuffer::GetBundledProgram(bundled_program_ptr),
method);
if (!method_test.ok()) {
return method_test.error();
}
auto bundled_inputs =
method_test.get()->test_cases()->Get(testset_idx)->inputs();
for (size_t input_idx = 0; input_idx < method.inputs_size(); input_idx++) {
auto bundled_input = bundled_inputs->GetMutableObject(input_idx);
// The EValue variable will contain the info set to input_idx Method input.
EValue e_input;
// Status for set_input function in this scope.
Error status;
// Set e_input with bundled_input based on different types.
switch (bundled_input->val_type()) {
case bundled_program_flatbuffer::ValueUnion::Tensor: {
auto bundled_input_tensor =
static_cast<bundled_program_flatbuffer::Tensor*>(
bundled_input->mutable_val());
#ifdef USE_ATEN_LIB
Tensor t = tensor_like(bundled_input_tensor);
#else // !USE_ATEN_LIB
TensorImpl impl = impl_like(bundled_input_tensor);
Tensor t = Tensor(&impl);
#endif
// Use t to create EValue as Method's input.
e_input = EValue(t);
// Setting input like this is safe because the `set_input` function only
// copies the underlying data blob of TensorImpl impl into the method,
// not the pointer of impl, Tensor t or even the Evalue e_input. So
// their lifetime will not impact the safety. Also there's a specific
// memory space with enough lifetime holding the underlying data blob,
// so the lifetime of the data blob is not an issue.
status = method.set_input(e_input, input_idx);
break;
}
case bundled_program_flatbuffer::ValueUnion::Int: {
auto bundled_input_int = bundled_input->val_as_Int();
e_input = EValue(bundled_input_int->int_val());
status = method.set_input(e_input, input_idx);
break;
}
case bundled_program_flatbuffer::ValueUnion::Double: {
auto bundled_input_int = bundled_input->val_as_Double();
e_input = EValue(bundled_input_int->double_val());
status = method.set_input(e_input, input_idx);
break;
}
case bundled_program_flatbuffer::ValueUnion::Bool: {
auto bundled_input_int = bundled_input->val_as_Bool();
e_input = EValue(bundled_input_int->bool_val());
status = method.set_input(e_input, input_idx);
break;
}
default: {
ET_CHECK_OR_RETURN_ERROR(
false,
NotSupported,
"Data type %hhu not supported",
static_cast<uint8_t>(bundled_input->val_type()));
break;
}
}
ET_CHECK_OR_RETURN_ERROR(
status == Error::Ok,
NotSupported,
"set_input failed during load bundled inputs with status 0%" PRIx32,
static_cast<uint32_t>(status));
}
::executorch::ET_RUNTIME_NAMESPACE::internal::
event_tracer_set_bundled_input_index(
method.get_event_tracer(), testset_idx);
return Error::Ok;
}
ET_NODISCARD Error verify_method_outputs(
Method& method,
SerializedBundledProgram* bundled_program_ptr,
size_t testset_idx,
double rtol,
double atol) {
ET_CHECK_OR_RETURN_ERROR(
bundled_program_flatbuffer::BundledProgramBufferHasIdentifier(
bundled_program_ptr),
NotSupported,
"The input buffer should be a bundled program.");
auto method_test = get_method_test_suite(
bundled_program_flatbuffer::GetBundledProgram(bundled_program_ptr),
method);
if (!method_test.ok()) {
return method_test.error();
}
auto bundled_expected_outputs =
method_test.get()->test_cases()->Get(testset_idx)->expected_outputs();
if (bundled_expected_outputs->size() == 0) {
// No bundled expected outputs, so we can't verify the method outputs.
return Error::NotSupported;
}
for (size_t output_idx = 0; output_idx < method.outputs_size();
output_idx++) {
auto bundled_expected_output =
bundled_expected_outputs->GetMutableObject(output_idx);
auto method_output = method.get_output(output_idx);
switch (bundled_expected_output->val_type()) {
case bundled_program_flatbuffer::ValueUnion::Tensor: {
auto bundled_expected_output_tensor =
static_cast<bundled_program_flatbuffer::Tensor*>(
bundled_expected_output->mutable_val());
const auto method_output_tensor = method_output.toTensor();
#ifdef USE_ATEN_LIB
Tensor t = tensor_like(bundled_expected_output_tensor);
#else // !USE_ATEN_LIB
TensorImpl impl = impl_like(bundled_expected_output_tensor);
Tensor t = Tensor(&impl);
#endif
ET_CHECK_OR_RETURN_ERROR(
tensors_are_close(t, method_output_tensor, rtol, atol),
NotFound, // maybe some new error tag?
"Method's output data mismatched the expected one.");
break;
}
default: {
ET_CHECK_OR_RETURN_ERROR(
false,
NotSupported,
"Data type %hhd not supported",
static_cast<uint8_t>(bundled_expected_output->val_type()));
break;
}
}
}
return Error::Ok;
}
ET_NODISCARD Error get_program_data(
void* file_data,
size_t file_data_len,
const void** out_program_data,
size_t* out_program_data_len) {
if (is_bundled_program(file_data, file_data_len)) {
auto program_bundled =
bundled_program_flatbuffer::GetBundledProgram(file_data);
*out_program_data = program_bundled->program()->data();
*out_program_data_len = program_bundled->program()->size();
} else {
ET_LOG(
Error,
"Unrecognized bundled program flatbuffer identifier '%.4s'",
flatbuffers::GetBufferIdentifier(file_data));
return Error::NotSupported;
}
return Error::Ok;
}
bool is_bundled_program(void* file_data, ET_UNUSED size_t file_data_len) {
// Even though the flatbuffer API doesn't accept a length, it's important to
// require one so that we could change the internal representation, or use a
// future API that does require a length.
return bundled_program_flatbuffer::BundledProgramBufferHasIdentifier(
file_data);
}
} // namespace BUNDLED_PROGRAM_NAMESPACE
} // namespace executorch