|
8 | 8 |
|
9 | 9 | #include <executorch/runtime/executor/tensor_parser.h> |
10 | 10 |
|
11 | | -#include <executorch/runtime/core/exec_aten/util/dim_order_util.h> |
12 | | -#include <executorch/runtime/core/exec_aten/util/scalar_type_util.h> |
13 | | -#include <executorch/runtime/core/named_data_map.h> |
| 11 | +#include <executorch/runtime/core/evalue.h> |
| 12 | +#include <executorch/runtime/core/exec_aten/exec_aten.h> |
14 | 13 | #include <executorch/runtime/executor/memory_manager.h> |
15 | 14 | #include <executorch/runtime/executor/program.h> |
16 | 15 | #include <executorch/runtime/platform/profiler.h> |
17 | 16 | #include <executorch/schema/program_generated.h> |
18 | 17 |
|
19 | | -#include <ATen/ATen.h> // @donotremove @manual=//caffe2/aten:ATen-core |
20 | | - |
21 | 18 | namespace executorch { |
22 | | -// This file is only used in ATen mode, so we use the runtime_aten namespace. |
23 | | -namespace runtime { |
24 | | -namespace aten { |
| 19 | +namespace ET_RUNTIME_NAMESPACE { |
25 | 20 | namespace deserialization { |
26 | 21 |
|
| 22 | +using executorch::aten::ScalarType; |
| 23 | +// Provides access to private Program methods. |
| 24 | +class TensorParser final { |
| 25 | + public: |
| 26 | + ET_NODISCARD static Error load_mutable_subsegment_into( |
| 27 | + const Program* program, |
| 28 | + size_t mutable_data_segments_index, |
| 29 | + size_t offset_index, |
| 30 | + size_t size, |
| 31 | + void* buffer) { |
| 32 | + return program->load_mutable_subsegment_into( |
| 33 | + mutable_data_segments_index, offset_index, size, buffer); |
| 34 | + } |
| 35 | +}; |
| 36 | + |
27 | 37 | namespace { |
28 | 38 |
|
29 | | -void deleteNothing(void*); |
30 | | -void deleteNothing(void*) {} |
| 39 | +// Retrieve the buffer specified by the allocation_info |
| 40 | +ET_NODISCARD Result<void*> getMemPlannedPtr( |
| 41 | + const executorch_flatbuffer::AllocationDetails* allocation_info, |
| 42 | + size_t nbytes, |
| 43 | + HierarchicalAllocator* allocator) { |
| 44 | + // Normal non-constant Tensor. Allocate data using mem_id and offset. |
31 | 45 |
|
| 46 | + // TODO(T142455629): make the allocator actually id based and not indexed |
| 47 | + // based. -1 is a hack to get the memory ids 0 aligned because previously |
| 48 | + // 0 was reserved |
| 49 | + const uint32_t memory_id = allocation_info->memory_id() - 1; |
| 50 | + |
| 51 | + // Originally this field was a single uint32_t, but we need 64 bits for |
| 52 | + // larger models. To preserve backwards compatibility, the high bits are |
| 53 | + // managed in a separate uint32_t field. |
| 54 | + const uint32_t memory_offset_low = allocation_info->memory_offset_low(); |
| 55 | + const uint32_t memory_offset_high = allocation_info->memory_offset_high(); |
| 56 | + |
| 57 | + size_t memory_offset = memory_offset_low; |
| 58 | + if ((sizeof(size_t) > sizeof(uint32_t)) && (memory_offset_high > 0)) { |
| 59 | + // The compiler should remove this always-true check on 64-bit systems. |
| 60 | + ET_CHECK_OR_RETURN_ERROR( |
| 61 | + sizeof(size_t) >= sizeof(uint64_t), |
| 62 | + NotSupported, |
| 63 | + "size_t cannot hold memory offset 0x%08" PRIx32 ".%08" PRIx32, |
| 64 | + memory_offset_high, |
| 65 | + memory_offset_low); |
| 66 | + memory_offset |= static_cast<size_t>(memory_offset_high) << 32; |
| 67 | + } |
| 68 | + return allocator->get_offset_address(memory_id, memory_offset, nbytes); |
| 69 | +} |
32 | 70 | } // namespace |
33 | 71 |
|
34 | | -Result<at::Tensor> parseTensor( |
35 | | - const Program* program, |
36 | | - MemoryManager* memory_manager, |
37 | | - const executorch_flatbuffer::Tensor* s_tensor, |
38 | | - const NamedDataMap* named_data_map, |
39 | | - Span<NamedData> external_constants) { |
40 | | - EXECUTORCH_SCOPE_PROF("TensorParser::parseTensor"); |
| 72 | +ET_NODISCARD Result<BoxedEvalueList<executorch::aten::Tensor>> parseTensorList( |
| 73 | + const flatbuffers::Vector<int32_t>* tensor_indices, |
| 74 | + EValue* values, |
| 75 | + size_t values_len, |
| 76 | + MemoryManager* memory_manager) { |
| 77 | + EXECUTORCH_SCOPE_PROF("TensorParser::parseTensorList"); |
41 | 78 |
|
42 | | - ET_CHECK_OR_RETURN_ERROR( |
43 | | - s_tensor->storage_offset() == 0, |
44 | | - NotSupported, |
45 | | - "Non-zero storage offset %" PRId32 " not supported", |
46 | | - s_tensor->storage_offset()); |
| 79 | + auto* tensor_list = |
| 80 | + memory_manager->method_allocator() |
| 81 | + ->allocateList<executorch::aten::Tensor>(tensor_indices->size()); |
| 82 | + if (tensor_list == nullptr) { |
| 83 | + return Error::MemoryAllocationFailed; |
| 84 | + } |
| 85 | + auto* evalp_list = memory_manager->method_allocator()->allocateList<EValue*>( |
| 86 | + tensor_indices->size()); |
| 87 | + if (evalp_list == nullptr) { |
| 88 | + return Error::MemoryAllocationFailed; |
| 89 | + } |
47 | 90 |
|
48 | | - // get metadata |
49 | | - at::ScalarType type = static_cast<at::ScalarType>(s_tensor->scalar_type()); |
50 | | - ET_CHECK_OR_RETURN_ERROR( |
51 | | - isValid(type), |
52 | | - InvalidProgram, |
53 | | - "Invalid ScalarType %" PRId8, |
54 | | - static_cast<int8_t>(type)); |
55 | | - auto options = at::CPU(type).options(); |
| 91 | + // For each tensor index look up the corresponding Tensor (which has been |
| 92 | + // already allocated) and stick it in the list. |
| 93 | + size_t output_idx = 0; |
| 94 | + for (int32_t tensor_index : *tensor_indices) { |
| 95 | + ET_CHECK_OR_RETURN_ERROR( |
| 96 | + tensor_index >= 0 && static_cast<size_t>(tensor_index) < values_len, |
| 97 | + InvalidProgram, |
| 98 | + "Invalid value index %" PRId32 " for TensorList", |
| 99 | + tensor_index); |
56 | 100 |
|
57 | | - ET_CHECK_OR_RETURN_ERROR( |
58 | | - s_tensor->sizes() != nullptr, InvalidProgram, "Missing sizes field"); |
59 | | - size_t ndim = s_tensor->sizes()->size(); |
| 101 | + // Placement new as the list elements are not initialized, so calling |
| 102 | + // copy assignment is not defined if it's non trivial. |
| 103 | + new (&tensor_list[output_idx]) executorch::aten::Tensor( |
| 104 | + values[static_cast<size_t>(tensor_index)].toTensor()); |
| 105 | + evalp_list[output_idx] = &values[static_cast<size_t>(tensor_index)]; |
| 106 | + output_idx++; |
| 107 | + } |
| 108 | + |
| 109 | + return BoxedEvalueList<executorch::aten::Tensor>( |
| 110 | + evalp_list, tensor_list, tensor_indices->size()); |
| 111 | +} |
60 | 112 |
|
| 113 | +ET_NODISCARD Error validateTensorLayout( |
| 114 | + const executorch_flatbuffer::Tensor* s_tensor, |
| 115 | + const TensorLayout& expected_layout) { |
61 | 116 | ET_CHECK_OR_RETURN_ERROR( |
62 | | - s_tensor->dim_order() != nullptr, |
63 | | - InvalidProgram, |
64 | | - "Missing dim_order field"); |
| 117 | + static_cast<ScalarType>(s_tensor->scalar_type()) == |
| 118 | + expected_layout.scalar_type(), |
| 119 | + InvalidExternalData, |
| 120 | + "Scalar type mismatch. Expected %hhd, got %hhd.", |
| 121 | + static_cast<int8_t>(s_tensor->scalar_type()), |
| 122 | + static_cast<int8_t>(expected_layout.scalar_type())); |
| 123 | + int dim = s_tensor->sizes()->size(); |
65 | 124 | ET_CHECK_OR_RETURN_ERROR( |
66 | | - s_tensor->dim_order()->size() == ndim, |
67 | | - InvalidProgram, |
68 | | - "dim_order size %" PRIu32 " != ndim %zu", |
69 | | - s_tensor->dim_order()->size(), |
70 | | - ndim); |
71 | | - |
72 | | - // convert int32 in serialization to int64 for aten |
73 | | - std::vector<int64_t> sizes( |
74 | | - s_tensor->sizes()->begin(), s_tensor->sizes()->end()); |
75 | | - std::vector<int64_t> strides(ndim); |
76 | | - auto status = dim_order_to_stride( |
77 | | - s_tensor->sizes()->data(), |
78 | | - s_tensor->dim_order()->data(), |
79 | | - ndim, |
80 | | - strides.data()); |
| 125 | + dim >= 0, InvalidExternalData, "Dim is negative: %d", dim) |
81 | 126 | ET_CHECK_OR_RETURN_ERROR( |
82 | | - status == Error::Ok, |
83 | | - Internal, |
84 | | - "dim_order_to_stride returned invalid status"); |
85 | | - |
86 | | - // Create a tensor without data first so we can find its expected size before |
87 | | - // getting its memory. |
88 | | - at::Tensor tensor = at::from_blob( |
89 | | - /*data=*/nullptr, |
90 | | - sizes, |
91 | | - strides, |
92 | | - /*storage_offset=*/0, |
93 | | - deleteNothing, |
94 | | - options); |
95 | | - |
96 | | - if (s_tensor->shape_dynamism() == |
97 | | - executorch_flatbuffer::TensorShapeDynamism::DYNAMIC_UNBOUND) { |
98 | | - // Provide fully dynamic tensors with an allocator so they can be resized |
99 | | - // within aten kernels. |
100 | | - auto impl = tensor.unsafeGetTensorImpl(); |
101 | | - at::StorageImpl* storage = impl->unsafe_storage().unsafeGetStorageImpl(); |
102 | | - storage->set_allocator(at::getCPUAllocator()); |
103 | | - storage->set_resizable(true); |
104 | | - storage->set_nbytes(0); |
105 | | - impl->set_sizes_contiguous(0); |
106 | | - // Leave the data as nullptr since it will be reallocated. |
107 | | - } else { |
108 | | - // Now that we know how big the tensor is, find and assign its memory. |
109 | | - Result<void*> data_ptr = getTensorDataPtr( |
110 | | - s_tensor, |
111 | | - program, |
112 | | - tensor.nbytes(), |
113 | | - memory_manager->planned_memory(), |
114 | | - named_data_map, |
115 | | - external_constants); |
116 | | - if (!data_ptr.ok()) { |
117 | | - ET_LOG( |
118 | | - Error, |
119 | | - "getTensorDataPtr() failed: 0x%" PRIx32, |
120 | | - static_cast<uint32_t>(data_ptr.error())); |
121 | | - return data_ptr.error(); |
| 127 | + static_cast<size_t>(dim) == expected_layout.sizes().size(), |
| 128 | + InvalidExternalData, |
| 129 | + "Dim mismatch. Expected %d, got %zu.", |
| 130 | + dim, |
| 131 | + expected_layout.sizes().size()); |
| 132 | + for (int i = 0; i < dim; i++) { |
| 133 | + ET_CHECK_OR_RETURN_ERROR( |
| 134 | + s_tensor->sizes()->Get(i) == expected_layout.sizes()[i], |
| 135 | + InvalidExternalData, |
| 136 | + "Sizes mismatch. Expected %d, got %d for size at index %d.", |
| 137 | + s_tensor->sizes()->Get(i), |
| 138 | + expected_layout.sizes()[i], |
| 139 | + i); |
| 140 | + ET_CHECK_OR_RETURN_ERROR( |
| 141 | + s_tensor->dim_order()->Get(i) == expected_layout.dim_order()[i], |
| 142 | + InvalidExternalData, |
| 143 | + "Dim order mismatch. Expected %d, got %d for dim at index %d.", |
| 144 | + s_tensor->dim_order()->Get(i), |
| 145 | + expected_layout.dim_order()[i], |
| 146 | + i); |
| 147 | + } |
| 148 | + return Error::Ok; |
| 149 | +} |
| 150 | + |
| 151 | +// Check if key exists in entries. If it does, return a pointer to the entry |
| 152 | +// otherwise return a nullptr. |
| 153 | +NamedData* get_data_by_key(const char* key, Span<NamedData> entries) { |
| 154 | + for (const auto i : c10::irange(entries.size())) { |
| 155 | + if (strcmp(key, entries[i].key) == 0) { |
| 156 | + return &entries[i]; |
122 | 157 | } |
123 | | - tensor.unsafeGetTensorImpl()->unsafe_storage().set_data_ptr( |
124 | | - at::DataPtr(data_ptr.get(), c10::DeviceType::CPU)); |
125 | 158 | } |
| 159 | + return nullptr; |
| 160 | +} |
126 | 161 |
|
127 | | - return tensor; |
| 162 | +ET_NODISCARD Result<void*> getTensorDataPtr( |
| 163 | + const executorch_flatbuffer::Tensor* s_tensor, |
| 164 | + const Program* program, |
| 165 | + size_t nbytes, |
| 166 | + HierarchicalAllocator* allocator, |
| 167 | + const NamedDataMap* named_data_map, |
| 168 | + Span<NamedData> external_constants) { |
| 169 | + auto data_buffer_idx = s_tensor->data_buffer_idx(); |
| 170 | + const executorch_flatbuffer::AllocationDetails* allocation_info = |
| 171 | + s_tensor->allocation_info(); |
| 172 | + |
| 173 | + // External tensors. |
| 174 | + if (s_tensor->extra_tensor_info() != nullptr && |
| 175 | + s_tensor->extra_tensor_info()->location() == |
| 176 | + executorch_flatbuffer::TensorDataLocation::EXTERNAL) { |
| 177 | + // Check that fqn is not null. |
| 178 | + ET_CHECK_OR_RETURN_ERROR( |
| 179 | + s_tensor->extra_tensor_info()->fully_qualified_name() != nullptr, |
| 180 | + InvalidExternalData, |
| 181 | + "Fully qualified name of external tensor is null"); |
| 182 | + const char* fqn = |
| 183 | + s_tensor->extra_tensor_info()->fully_qualified_name()->c_str(); |
| 184 | + |
| 185 | + // Constant value. |
| 186 | + if (allocation_info == nullptr) { |
| 187 | + NamedData* data = get_data_by_key(fqn, external_constants); |
| 188 | + if (data != nullptr) { |
| 189 | + return const_cast<void*>(data->buffer.data()); |
| 190 | + } |
| 191 | + // Should never reach here; these tensors are resolved in |
| 192 | + // Method::parse_external_constants. Any errors should be caught there. |
| 193 | + return Error::Internal; |
| 194 | + } else { |
| 195 | + // Mutable value. |
| 196 | + // Look up tensor in named data map. |
| 197 | + ET_CHECK_OR_RETURN_ERROR( |
| 198 | + named_data_map != nullptr, |
| 199 | + InvalidExternalData, |
| 200 | + "Cannot retrieve external tensor with fqn: %s. The named_data_map is null; most likely no external .ptd file was provided.", |
| 201 | + fqn); |
| 202 | + Result<const TensorLayout> tensor_layout_res = |
| 203 | + named_data_map->get_tensor_layout(fqn); |
| 204 | + if (!tensor_layout_res.ok()) { |
| 205 | + return tensor_layout_res.error(); |
| 206 | + } |
| 207 | + const TensorLayout& tensor_layout = tensor_layout_res.get(); |
| 208 | + Error err = validateTensorLayout(s_tensor, tensor_layout); |
| 209 | + if (err != Error::Ok) { |
| 210 | + return err; |
| 211 | + } |
| 212 | + // Call load_into. |
| 213 | + auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator); |
| 214 | + if (!planned_ptr.ok()) { |
| 215 | + return planned_ptr.error(); |
| 216 | + } |
| 217 | + auto load_error = |
| 218 | + named_data_map->load_data_into(fqn, planned_ptr.get(), nbytes); |
| 219 | + if (load_error != Error::Ok) { |
| 220 | + return load_error; |
| 221 | + } |
| 222 | + |
| 223 | + return planned_ptr; |
| 224 | + } |
| 225 | + |
| 226 | + // Constant, stored in PTE file. |
| 227 | + } else if (data_buffer_idx > 0 && allocation_info == nullptr) { |
| 228 | + auto const_data = |
| 229 | + program->get_constant_buffer_data(data_buffer_idx, nbytes); |
| 230 | + if (!const_data.ok()) { |
| 231 | + return const_data.error(); |
| 232 | + } |
| 233 | + |
| 234 | + // The const_cast is 'ok' here because the program and runtime should |
| 235 | + // guarantee that this data is never modified. |
| 236 | + return const_cast<void*>(const_data.get()); |
| 237 | + |
| 238 | + // Memory Planned, with initial state |
| 239 | + } else if (data_buffer_idx > 0 && allocation_info != nullptr) { |
| 240 | + auto planned_ptr = getMemPlannedPtr(allocation_info, nbytes, allocator); |
| 241 | + if (!planned_ptr.ok()) { |
| 242 | + return planned_ptr.error(); |
| 243 | + } |
| 244 | + auto err = TensorParser::load_mutable_subsegment_into( |
| 245 | + program, 0, s_tensor->data_buffer_idx(), nbytes, planned_ptr.get()); |
| 246 | + |
| 247 | + if (err != Error::Ok) { |
| 248 | + return err; |
| 249 | + } |
| 250 | + return planned_ptr; |
| 251 | + |
| 252 | + // Memory planned, no initial state |
| 253 | + } else if (data_buffer_idx == 0 && allocation_info != nullptr) { |
| 254 | + return getMemPlannedPtr(allocation_info, nbytes, allocator); |
| 255 | + |
| 256 | + // Pointer recived at runtime |
| 257 | + } else { // data_buffer_idx == 0 && allocation_info == nullptr, |
| 258 | + return nullptr; |
| 259 | + } |
128 | 260 | } |
129 | 261 |
|
130 | 262 | } // namespace deserialization |
131 | | -} // namespace aten |
132 | | -} // namespace runtime |
| 263 | +} // namespace ET_RUNTIME_NAMESPACE |
133 | 264 | } // namespace executorch |
0 commit comments