2 * Copyright (c) 2022 Samsung Electronics Co., Ltd. All Rights Reserved
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "Executors.h"
19 #include "../backend/builtin/IOTensor.h"
24 using namespace onert;
26 int32_t find_input_index(const std::vector<ir::IODesc> &pkg_inputs,
27 const ir::ModelIndex &model_index, const ir::SubgraphIndex &subg_index,
28 const ir::IOIndex &io_index)
30 for (size_t i = 0; i < pkg_inputs.size(); i++)
32 auto &input_desc = pkg_inputs[i];
33 if ((std::get<ir::ModelIndex>(input_desc) == model_index) &&
34 (std::get<ir::SubgraphIndex>(input_desc) == subg_index) &&
35 (std::get<ir::IOIndex>(input_desc) == io_index))
36 return static_cast<int32_t>(i);
41 int32_t find_output_index(const std::vector<ir::IODesc> &pkg_outputs,
42 const ir::ModelIndex &model_index, const ir::SubgraphIndex &subg_index,
43 const ir::IOIndex &io_index)
45 for (size_t i = 0; i < pkg_outputs.size(); i++)
47 auto &input_desc = pkg_outputs[i];
48 if ((std::get<ir::ModelIndex>(input_desc) == model_index) &&
49 (std::get<ir::SubgraphIndex>(input_desc) == subg_index) &&
50 (std::get<ir::IOIndex>(input_desc) == io_index))
51 return static_cast<int32_t>(i);
63 class Executors::EdgeTensor : public backend::builtin::IOTensor
66 EdgeTensor(const ir::OperandInfo &info, ir::Layout layout)
67 : backend::builtin::IOTensor(info, layout), _buffer{nullptr}, _ref_count{0}
70 ~EdgeTensor() = default;
72 void allocate_buffer()
74 const auto total_size = orig_info().total_size();
75 _buffer = std::make_unique<uint8_t[]>(total_size);
78 // NOTE Executor's inputs/outputs are always IPortableTensor. If backend of inputs/outputs
79 // is using tensor that does not inherit IPortableTensor, Permute operation is added
80 // and all inputs/outputs become IPortableTensor at compile stage.
81 // This allows user's buffers to be set to inputs/outputs of executors.
82 setUserTensor(_buffer.get(), total_size);
85 void increase_ref() { _ref_count++; }
89 assert(_ref_count > 0);
94 setUserTensor(nullptr, orig_info().total_size());
99 std::unique_ptr<uint8_t[]> _buffer;
103 void Executors::emplace(const ir::ModelIndex &model_index, const ir::SubgraphIndex &subg_index,
104 std::unique_ptr<IExecutor> exec)
106 _executors.emplace(std::make_pair(model_index, subg_index), std::move(exec));
109 IExecutor *Executors::at(const ir::ModelIndex &model_index,
110 const ir::SubgraphIndex &subg_index) const
112 return _executors.at(std::make_pair(model_index, subg_index)).get();
115 uint32_t Executors::inputSize() const { return _model_edges->pkg_inputs.size(); }
117 uint32_t Executors::outputSize() const { return _model_edges->pkg_outputs.size(); }
119 const ir::OperandInfo &Executors::inputInfo(const ir::IOIndex &index) const
121 auto const desc = _model_edges->pkg_inputs[index.value()];
122 auto const model_index = std::get<0>(desc);
123 auto const subg_index = std::get<1>(desc);
124 auto const io_index = std::get<2>(desc);
125 auto const executor = at(model_index, subg_index);
126 return executor->getInputTensors().at(io_index.value())->orig_info();
129 const ir::OperandInfo &Executors::outputInfo(const ir::IOIndex &index) const
131 auto const desc = _model_edges->pkg_outputs[index.value()];
132 auto const model_index = std::get<0>(desc);
133 auto const subg_index = std::get<1>(desc);
134 auto const io_index = std::get<2>(desc);
135 auto const executor = at(model_index, subg_index);
136 return executor->getOutputTensors().at(io_index.value())->orig_info();
139 // Allow below edges only
140 // m1 < m2, s1 == 0 and s2 == 0 if m1:s1:o1 -> m2:s2:o2'
141 void Executors::checkSupportedMultimodel() const
143 // If package includes no-connection model, model_count is less than real model count in package.
144 // Then this method will throw exception based on model index
145 // 1st model: input assumption
146 // Otherwise: edges assumption
149 // m1 < m2, s1 == 0 and s2 == 0 if edge 'm1:s1:o1 -> m2:s2:o2'
150 for (auto &&edge : _model_edges->edges)
152 auto const model_from = std::get<ir::ModelIndex>(edge.from);
153 auto const model_to = std::get<ir::ModelIndex>(edge.to);
154 auto const subg_from = std::get<ir::SubgraphIndex>(edge.from);
155 auto const subg_to = std::get<ir::SubgraphIndex>(edge.to);
157 if (model_from.value() == model_to.value())
159 throw std::runtime_error{"Multi model's edge set has invalid edge"};
162 if ((model_from.value() > model_to.value()) || (subg_from != ir::SubgraphIndex{0}) ||
163 (subg_to != ir::SubgraphIndex{0}))
164 throw std::runtime_error{"NYI: Multi model execution for this edge set is not supported yet"};
167 // Assumption: package inputs
168 // All 1st model inputs come from package input if always m1 < m2
170 auto first_executor = at(ir::ModelIndex{0}, ir::SubgraphIndex{0});
171 auto search_first_model = [&](const ir::IOIndex &input_index) {
172 for (const auto &input : _model_edges->pkg_inputs)
174 if ((std::get<ir::ModelIndex>(input) == ir::ModelIndex{0}) ||
175 (std::get<ir::SubgraphIndex>(input) == ir::SubgraphIndex{0}) ||
176 (std::get<ir::IOIndex>(input) == input_index))
183 for (uint32_t i = 0; i < first_executor->getInputTensors().size(); i++)
185 if (!search_first_model(ir::IOIndex{i}))
186 throw std::runtime_error{"Cannot find 1st model's input buffer"};
190 // Check whether nnpkg outputs and Edge `from` are duplicated
191 for (const auto &edge : _model_edges->edges)
193 if (std::find(_model_edges->pkg_outputs.begin(), _model_edges->pkg_outputs.end(), edge.from) !=
194 _model_edges->pkg_outputs.end())
196 throw std::runtime_error{"Multi model execution does not support duplicating nnpkg outputs "
197 "with `from` of edges yet"};
202 void Executors::createEdgeQuantLayers()
204 if (_is_created_edge_quant_layers)
209 // Create EdgeTensor for edges between executors
210 for (const auto &pair : _edge_map)
212 const auto &from_iodesc = pair.first;
213 const auto &from_model_index = std::get<ir::ModelIndex>(from_iodesc);
214 const auto &from_subg_index = std::get<ir::SubgraphIndex>(from_iodesc);
215 const auto &from_io_index = std::get<ir::IOIndex>(from_iodesc);
217 const auto from_executor = _executors.at({from_model_index, from_subg_index}).get();
218 const auto from_tensor = from_executor->getOutputTensors().at(from_io_index.value());
220 const auto &from_info = from_tensor->orig_info();
221 const auto from_layout = from_tensor->orig_layout();
222 _edge_tensors[from_iodesc] = std::make_unique<EdgeTensor>(from_info, from_layout);
225 // Append type-aware quantization layer for edges between executors
226 for (const auto &executor_pair : _executors)
228 const auto &executor_index = executor_pair.first;
229 const auto &model_index = executor_index.first;
230 const auto &subg_index = executor_index.second;
232 std::vector<backend::ITensor *> inputs;
233 std::vector<backend::ITensor *> outputs;
234 for (const auto &pair : _edge_map)
236 const auto &from_iodesc = pair.first;
237 if (std::get<ir::ModelIndex>(from_iodesc) == model_index &&
238 std::get<ir::SubgraphIndex>(from_iodesc) == subg_index)
240 const auto from_tensor = _edge_tensors[from_iodesc].get();
241 const auto &to_list = pair.second;
243 for (const auto &to_iodesc : to_list)
245 const auto &to_model_index = std::get<ir::ModelIndex>(to_iodesc);
246 const auto &to_subg_index = std::get<ir::SubgraphIndex>(to_iodesc);
247 const auto &to_io_index = std::get<ir::IOIndex>(to_iodesc);
249 const auto to_executor = _executors.at({to_model_index, to_subg_index}).get();
250 const auto to_tensor = to_executor->getInputTensors().at(to_io_index.value());
252 // TODO Unify tensors with the same `from` tensor and same type
253 if (from_tensor->data_type() != to_tensor->data_type())
255 assert(inputs.size() == outputs.size());
256 const auto &to_info =
257 to_executor->getInputTensors().at(to_io_index.value())->orig_info();
258 const auto to_layout = to_tensor->orig_layout();
259 inputs.emplace_back(from_tensor);
261 auto type_aware_quant_tensor = std::make_unique<EdgeTensor>(to_info, to_layout);
262 outputs.emplace_back(type_aware_quant_tensor.get());
264 _edge_quant_tensors[to_iodesc] = std::move(type_aware_quant_tensor);
270 auto layer = std::make_unique<PermuteLayer>(inputs, outputs);
272 _edge_quant_layers[{model_index, subg_index}] = std::move(layer);
275 _is_created_edge_quant_layers = true;
278 void Executors::CreatePkgIOTensors(const IODescription &desc)
280 for (const auto &pkg_input : _model_edges->pkg_inputs)
282 // Create IOTensor for nnpkg inputs
283 const auto &model_index = std::get<ir::ModelIndex>(pkg_input);
284 const auto &subg_index = std::get<ir::SubgraphIndex>(pkg_input);
285 const auto &io_index = std::get<ir::IOIndex>(pkg_input);
286 const auto input_pkg_index =
287 find_input_index(_model_edges->pkg_inputs, model_index, subg_index, io_index);
288 if (input_pkg_index == -1)
289 throw std::runtime_error{"Cannot find multi model input index"};
290 auto input_desc = desc.inputs[input_pkg_index].get();
291 _pkg_input_tensors[pkg_input] =
292 std::make_unique<backend::builtin::IOTensor>(input_desc->info, input_desc->layout);
295 for (const auto &pkg_output : _model_edges->pkg_outputs)
297 // Create IOTensor for nnpkg outputs
298 const auto &model_index = std::get<ir::ModelIndex>(pkg_output);
299 const auto &subg_index = std::get<ir::SubgraphIndex>(pkg_output);
300 const auto &io_index = std::get<ir::IOIndex>(pkg_output);
301 const auto output_pkg_index =
302 find_output_index(_model_edges->pkg_outputs, model_index, subg_index, io_index);
303 if (output_pkg_index == -1)
304 throw std::runtime_error{"Cannot find multi model output index"};
305 auto output_desc = desc.outputs[output_pkg_index].get();
306 _pkg_output_tensors[pkg_output] =
307 std::make_unique<backend::builtin::IOTensor>(output_desc->info, output_desc->layout);
311 void Executors::createPkgIOQuantLayers(const IODescription &desc)
313 // Append type-aware quantization layer for nnpkg inputs/outputs between executors
314 for (const auto &pair : _executors)
316 const auto &executor_index = pair.first;
317 const auto &model_index = executor_index.first;
318 const auto &subg_index = executor_index.second;
319 const auto executor = pair.second.get();
321 // Find pkg inputs of current executor
322 std::vector<ir::IODesc> pkg_inputs;
323 for (const auto &pkg_input : _model_edges->pkg_inputs)
325 if (std::get<ir::ModelIndex>(pkg_input) == model_index &&
326 std::get<ir::SubgraphIndex>(pkg_input) == subg_index)
328 pkg_inputs.emplace_back(pkg_input);
331 std::vector<backend::ITensor *> src_tensors;
332 std::vector<backend::ITensor *> dst_tensors;
333 for (const auto &pkg_input : pkg_inputs)
335 const auto &io_index = std::get<ir::IOIndex>(pkg_input);
336 const auto input_pkg_index =
337 find_input_index(_model_edges->pkg_inputs, model_index, subg_index, io_index);
338 if (input_pkg_index == -1)
339 throw std::runtime_error{"Cannot find multi model input index"};
340 auto input_desc = desc.inputs[input_pkg_index].get();
342 // Create EdgeTensor for nnpkg input if type is different
343 const auto input_tensor =
344 executor->getInputTensors().at(std::get<ir::IOIndex>(pkg_input).value());
345 const auto &orig_info = input_tensor->orig_info();
346 if (input_desc->info.typeInfo().type() != input_tensor->orig_info().typeInfo().type())
348 const auto orig_layout = input_tensor->orig_layout();
349 auto pkg_input_edge_tensor = std::make_unique<EdgeTensor>(orig_info, orig_layout);
350 _pkg_input_quant_tensors[pkg_input] = std::move(pkg_input_edge_tensor);
352 // Append type-aware quantization layer's inputs/outputs
353 src_tensors.emplace_back(_pkg_input_tensors[pkg_input].get());
354 dst_tensors.emplace_back(_pkg_input_quant_tensors[pkg_input].get());
358 // Create type-aware quantization layer for nnpkg inputs
359 auto pkg_input_layer = std::make_unique<PermuteLayer>(src_tensors, dst_tensors);
360 pkg_input_layer->prepare();
361 _pkg_input_quant_layers[{model_index, subg_index}] = std::move(pkg_input_layer);
363 // Find pkg outputs of current executor
364 std::vector<ir::IODesc> pkg_outputs;
365 for (const auto &pkg_output : _model_edges->pkg_outputs)
367 if (std::get<ir::ModelIndex>(pkg_output) == model_index &&
368 std::get<ir::SubgraphIndex>(pkg_output) == subg_index)
370 pkg_outputs.emplace_back(pkg_output);
375 // Create Tensors of nnpkg outputs for type-aware quantization
376 for (const auto &pkg_output : pkg_outputs)
378 const auto &io_index = std::get<ir::IOIndex>(pkg_output);
379 const auto output_pkg_index =
380 find_output_index(_model_edges->pkg_outputs, model_index, subg_index, io_index);
381 if (output_pkg_index == -1)
382 throw std::runtime_error{"Cannot find multi model output index"};
383 auto output_desc = desc.outputs[output_pkg_index].get();
385 // Create EdgeTensor for nnpkg output if type is different
386 const auto output_tensor =
387 executor->getOutputTensors().at(std::get<ir::IOIndex>(pkg_output).value());
388 const auto &orig_info = output_tensor->orig_info();
389 if (output_desc->info.typeInfo().type() != output_tensor->orig_info().typeInfo().type())
391 const auto orig_layout = output_tensor->orig_layout();
392 auto pkg_output_edge_tensor = std::make_unique<EdgeTensor>(orig_info, orig_layout);
393 _pkg_output_quant_tensors[pkg_output] = std::move(pkg_output_edge_tensor);
395 // Append type-aware quantization layer's inputs/outputs
396 src_tensors.emplace_back(_pkg_output_quant_tensors[pkg_output].get());
397 dst_tensors.emplace_back(_pkg_output_tensors[pkg_output].get());
401 // Create type-aware quantization layer for nnpkg outputs
402 auto pkg_output_layer = std::make_unique<PermuteLayer>(src_tensors, dst_tensors);
403 pkg_output_layer->prepare();
404 _pkg_output_quant_layers[{model_index, subg_index}] = std::move(pkg_output_layer);
408 void Executors::execute(const IODescription &desc)
410 // Check supported multi model package
411 checkSupportedMultimodel();
413 // TODO Move creating type-aware quantization layers for edges in compilation stage
414 createEdgeQuantLayers();
416 // TODO Create IOTensors only once and recreate them only if nnpkg info changes
417 CreatePkgIOTensors(desc);
419 // TODO Create type-aware quantization layers only once and recreate them only if type changes
420 createPkgIOQuantLayers(desc);
422 // TODO Find better way to schedule order of executors
423 auto const model_count = modelCount();
425 auto find_from = [&](const ir::ModelIndex &model_index, const ir::SubgraphIndex &subg_index,
426 const ir::IOIndex &io_index) {
427 for (const auto &edge : _model_edges->edges)
429 if ((std::get<ir::ModelIndex>(edge.to) == model_index) &&
430 (std::get<ir::SubgraphIndex>(edge.to) == subg_index) &&
431 (std::get<ir::IOIndex>(edge.to) == io_index))
435 throw std::runtime_error{"Cannot find edge for model input"};
438 // Execute each model
439 // NOTE May be better to use vector instead of unordered_map for _executors
440 for (auto model_index = ir::ModelIndex{0}; model_index.value() < model_count; model_index++)
443 auto executor = at(model_index, ir::SubgraphIndex{0});
446 // TODO Set internal IOTensors only once
447 std::vector<backend::IPortableTensor *> inputs_inter;
448 std::vector<backend::IPortableTensor *> outputs_inter;
449 const auto &input_tensors = executor->getInputTensors();
450 const auto &output_tensors = executor->getOutputTensors();
451 auto const input_size = input_tensors.size();
452 auto const output_size = output_tensors.size();
453 inputs_inter.resize(input_size);
454 outputs_inter.resize(output_size);
456 // Set inputs of executor
457 // TODO Create layer to allocate/deallocate buffers of EdgeTensor for each executor
458 for (uint32_t i = 0; i < input_size; i++)
460 const auto input_pkg_index = find_input_index(_model_edges->pkg_inputs, model_index,
461 ir::SubgraphIndex{0}, ir::IOIndex{i});
462 const auto input_io_desc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
463 if (input_pkg_index != -1)
465 // Allocate type-aware quantization tensors for nnpkg inputs and set internal tensors
466 if (_pkg_input_quant_tensors.find(input_io_desc) != _pkg_input_quant_tensors.end())
468 _pkg_input_quant_tensors[input_io_desc]->allocate_buffer();
470 inputs_inter[i] = _pkg_input_quant_tensors[input_io_desc].get();
474 inputs_inter[i] = _pkg_input_tensors[input_io_desc].get();
477 // Set buffer of IOTensor
478 auto input_desc = desc.inputs[input_pkg_index].get();
479 // TODO Remove const_cast (we need const_cast as ITensor is writable)
480 _pkg_input_tensors[input_io_desc]->setUserTensor(
481 reinterpret_cast<uint8_t *>(const_cast<void *>(input_desc->buffer)), input_desc->size);
485 auto from_iodesc = find_from(model_index, ir::SubgraphIndex{0}, ir::IOIndex{i});
486 const auto &from_model_index = std::get<ir::ModelIndex>(from_iodesc);
487 const auto &from_subg_index = std::get<ir::SubgraphIndex>(from_iodesc);
488 const auto &from_ioindex = std::get<ir::IOIndex>(from_iodesc).value();
490 // Supported only sequantial execution of models
491 assert(from_model_index.value() < model_index.value());
492 assert(from_subg_index.value() == 0);
493 const auto from_executor = _executors.at({from_model_index, from_subg_index}).get();
494 const auto to_iodesc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
495 if (_edge_quant_tensors.find(to_iodesc) == _edge_quant_tensors.end())
497 inputs_inter[i] = from_executor->getOutputTensors().at(from_ioindex);
501 inputs_inter[i] = _edge_quant_tensors.at(to_iodesc).get();
503 assert(inputs_inter[i]->buffer() != nullptr);
507 // Set outputs of executor
508 for (uint32_t i = 0; i < output_size; i++)
510 const auto output_pkg_index = find_output_index(_model_edges->pkg_outputs, model_index,
511 ir::SubgraphIndex{0}, ir::IOIndex{i});
512 const auto output_io_desc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
513 if (output_pkg_index != -1)
515 // Allocate type-aware quantization tensors for nnpkg outputs and set internal tensors
516 if (_pkg_output_quant_tensors.find(output_io_desc) != _pkg_output_quant_tensors.end())
518 _pkg_output_quant_tensors[output_io_desc]->allocate_buffer();
520 outputs_inter[i] = _pkg_output_quant_tensors[output_io_desc].get();
524 outputs_inter[i] = _pkg_output_tensors[output_io_desc].get();
527 // Set buffer of IOTensor
528 auto output_desc = desc.outputs[output_pkg_index].get();
529 _pkg_output_tensors[output_io_desc]->setUserTensor(
530 reinterpret_cast<uint8_t *>(output_desc->buffer), output_desc->size);
534 // Allocate buffer of `from` tensors
535 const auto from_iodesc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
536 _edge_tensors[from_iodesc]->allocate_buffer();
537 outputs_inter[i] = _edge_tensors[from_iodesc].get();
539 // Allocate buffer of tensors for type-aware quantization
540 for (const auto &to_iodesc : _edge_map[from_iodesc])
542 _edge_tensors[from_iodesc]->increase_ref();
543 if (_edge_quant_tensors.find(to_iodesc) != _edge_quant_tensors.end())
545 auto type_aware_quant_tensor = _edge_quant_tensors.at(to_iodesc).get();
546 type_aware_quant_tensor->allocate_buffer();
548 _edge_tensors[from_iodesc]->decrease_ref();
554 _pkg_input_quant_layers[{model_index, ir::SubgraphIndex{0}}]->run();
556 executor->execute(inputs_inter, outputs_inter);
558 _edge_quant_layers[{model_index, ir::SubgraphIndex{0}}]->run();
559 _pkg_output_quant_layers[{model_index, ir::SubgraphIndex{0}}]->run();
561 // Release input buffers that are no longer needed
562 for (uint32_t i = 0; i < input_size; i++)
564 const auto input_pkg_index = find_input_index(_model_edges->pkg_inputs, model_index,
565 ir::SubgraphIndex{0}, ir::IOIndex{i});
567 const auto to_iodesc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
568 if (input_pkg_index == -1)
570 if (_edge_quant_tensors.find(to_iodesc) != _edge_quant_tensors.end())
572 // Decrease reference count of tensor for type-aware quantization if input tensor is the
574 const auto to_iodesc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
575 if (_edge_quant_tensors.find(to_iodesc) != _edge_quant_tensors.end())
577 _edge_quant_tensors[to_iodesc]->decrease_ref();
582 // Decrease reference count of `from` tensor if input tensor is the `from` tensor
583 const auto from_iodesc = find_from(model_index, ir::SubgraphIndex{0}, ir::IOIndex{i});
584 _edge_tensors[from_iodesc]->decrease_ref();
586 // Decrease reference count of nnpkg inputs
587 if (_pkg_input_quant_tensors.find(to_iodesc) != _pkg_input_quant_tensors.end())
589 _pkg_input_quant_tensors[to_iodesc]->decrease_ref();
595 // Release output buffers if those buffers are no longer used other executors because of
596 // type-aware quantization
597 // FIXME if tensors for type-aware quantization unified for the same `from` tensor and same type
598 for (uint32_t i = 0; i < output_size; i++)
600 auto from_iodesc = ir::IODesc{model_index, ir::SubgraphIndex{0}, ir::IOIndex{i}};
602 // Check if other executors will use the buffer of edge tensor
603 const auto &to_list = _edge_map[from_iodesc];
604 if (to_list.size() == 0)
606 // This condition means `from_iodesc` tensor is an output of nnpkg
611 !std::any_of(to_list.begin(), to_list.end(), [&](const ir::IODesc &to_iodesc) {
612 // This condition means another executor uses the buffer of edge tensor
613 return _edge_quant_tensors.find(to_iodesc) == _edge_quant_tensors.end();
618 // This edge tensor's buffer won't be used in other executors
619 // Tensors for type-aware quantization take over the role of this edge tensor instead
620 _edge_tensors[from_iodesc]->decrease_ref();
623 // Decrease reference count of nnpkg outputs
624 if (_pkg_output_quant_tensors.find(from_iodesc) != _pkg_output_quant_tensors.end())
626 _pkg_output_quant_tensors[from_iodesc]->decrease_ref();
632 // modelCount() iterates _executors.
633 // It assumes that Compiler will generate Executor for all models and _executors includes all
634 // generated Executor.
635 // If nnpackage includes model(s) which has no connection and Compiler does not
636 // generate Executor for them, modelCount() return less value than real model count.
637 uint16_t Executors::modelCount() const
639 uint16_t model_count = 0;
640 for (; _executors.find(std::make_pair(ir::ModelIndex{model_count}, ir::SubgraphIndex{0})) !=