2 * Copyright (c) 2019 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 "nnfw_api_internal.h"
18 #include "CustomKernelRegistry.h"
19 #include "compiler/Compiler.h"
20 #include "util/ConfigSource.h"
21 #include "util/Exceptions.h"
22 #include "exec/Execution.h"
23 #include "circle_loader.h"
24 #include "tflite_loader.h"
25 #include "json/json.h"
26 #include "ir/OpCode.h"
32 #include <util/ConfigSource.h>
33 #include <misc/string_helpers.h>
36 * API does not accept string argument longer than max length below
38 #define MAX_BACKEND_NAME_LENGTH 32
39 #define MAX_OP_NAME_LENGTH 64
40 #define MAX_PATH_LENGTH 1024
41 #define MAX_TENSOR_NAME_LENGTH 64
43 // Is null-terminating in length ?
44 static bool null_terminating(const char *str, uint32_t length)
46 for (uint32_t i = 0; i < length; i++)
48 if (*(str + i) == '\0')
56 static onert::ir::Layout convertLayout(NNFW_LAYOUT layout)
58 if (layout == NNFW_LAYOUT_CHANNELS_LAST)
60 return onert::ir::Layout::NHWC;
62 else if (layout == NNFW_LAYOUT_CHANNELS_FIRST)
64 return onert::ir::Layout::NCHW;
66 return onert::ir::Layout::UNKNOWN;
69 NNFW_STATUS getTensorIndexImpl(const onert::ir::Graph &graph, const char *tensorname,
70 uint32_t *index, bool is_input)
72 if (!tensorname || !index)
73 return NNFW_STATUS_UNEXPECTED_NULL;
75 if (!null_terminating(tensorname, MAX_TENSOR_NAME_LENGTH))
77 std::cerr << "nnpackage path is too long" << std::endl;
78 return NNFW_STATUS_ERROR;
81 auto ind_found = is_input ? graph.getInputIndex(tensorname) : graph.getOutputIndex(tensorname);
83 if (ind_found.undefined())
86 return NNFW_STATUS_ERROR;
90 *index = ind_found.value();
91 return NNFW_STATUS_NO_ERROR;
95 nnfw_session::nnfw_session()
96 : _subgraphs{nullptr}, _execution{nullptr},
97 _kernel_registry{std::make_shared<onert::frontend::custom::KernelRegistry>()}
102 nnfw_session::~nnfw_session() = default;
104 NNFW_STATUS nnfw_session::load_circle_from_buffer(uint8_t *buffer, size_t size)
106 if (!isStateInitialized())
107 return NNFW_STATUS_INVALID_STATE;
110 return NNFW_STATUS_UNEXPECTED_NULL;
113 return NNFW_STATUS_ERROR;
117 _subgraphs = onert::circle_loader::loadModel(buffer, size);
119 catch (const std::exception &e)
121 std::cerr << "Error during model loading : " << e.what() << std::endl;
122 return NNFW_STATUS_ERROR;
125 _compiler = std::make_unique<onert::compiler::Compiler>(_subgraphs);
127 _state = State::MODEL_LOADED;
128 return NNFW_STATUS_NO_ERROR;
131 NNFW_STATUS nnfw_session::load_model_from_file(const char *package_dir)
133 if (!isStateInitialized())
134 return NNFW_STATUS_INVALID_STATE;
138 std::cerr << "package_dir is null." << std::endl;
139 return NNFW_STATUS_UNEXPECTED_NULL;
142 if (!null_terminating(package_dir, MAX_PATH_LENGTH))
144 std::cerr << "nnpackage path is too long" << std::endl;
145 return NNFW_STATUS_ERROR;
148 // TODO : add support for zipped package file load
150 if (!(dir = opendir(package_dir)))
152 std::cerr << "invalid nnpackge directory: " << package_dir << std::endl;
153 return NNFW_STATUS_ERROR;
159 std::string manifest_file_name(package_dir);
160 manifest_file_name += "/metadata/MANIFEST";
161 std::ifstream mfs(manifest_file_name);
163 // extract the filename of the first(index 0) model
164 // e.g. In MANIFEST file, { "models" : [ "firstmodel.tflite", "2nd.tflite" ] }
167 const Json::Value &models = root["models"];
168 const Json::Value &model_types = root["model-types"];
170 auto model_file_path = package_dir + std::string("/") + models[0].asString(); // first model
171 auto model_type = model_types[0].asString(); // first model's type
172 if (model_type == "tflite")
174 _subgraphs = onert::tflite_loader::loadModel(model_file_path.c_str());
176 else if (model_type == "circle")
178 _subgraphs = onert::circle_loader::loadModel(model_file_path.c_str());
182 std::cerr << "Unsupported model type in MANIFEST" << std::endl;
183 return NNFW_STATUS_ERROR;
185 _subgraphs->primary()->bindKernelBuilder(_kernel_registry->getBuilder());
187 catch (const std::exception &e)
189 std::cerr << "Error during model loading : " << e.what() << std::endl;
190 return NNFW_STATUS_ERROR;
193 _compiler = std::make_unique<onert::compiler::Compiler>(_subgraphs);
195 _state = State::MODEL_LOADED;
196 return NNFW_STATUS_NO_ERROR;
199 NNFW_STATUS nnfw_session::prepare()
201 // NOTE. If users want to run prepare() more than one time, this could be removed.
202 if (!isStateModelLoaded())
204 std::cerr << "Error during model prepare : ";
205 if (isStateInitialized())
207 std::cerr << "prepare should be run once";
211 std::cerr << "invalid state";
213 std::cerr << std::endl;
214 return NNFW_STATUS_INVALID_STATE;
217 if (!_subgraphs || !primary_subgraph() || primary_subgraph()->isBuildingPhase())
219 std::cerr << "Error during model prepare : "
220 << "prepare should be run after load_model" << std::endl;
221 return NNFW_STATUS_ERROR;
227 std::shared_ptr<onert::exec::ExecutorMap> executors = _compiler->compile();
228 _execution = std::make_shared<onert::exec::Execution>(executors);
230 catch (const std::exception &e)
232 std::cerr << "Error during model prepare : " << e.what() << std::endl;
233 return NNFW_STATUS_ERROR;
236 _state = State::PREPARED;
237 return NNFW_STATUS_NO_ERROR;
240 NNFW_STATUS nnfw_session::run()
242 if (!isStatePreparedOrFinishedRun())
244 std::cerr << "Error during nnfw_session::run : "
245 << "run should be run after prepare" << std::endl;
246 return NNFW_STATUS_INVALID_STATE;
251 _execution->execute();
253 catch (const onert::InsufficientBufferSizeException &e)
255 // Currently insufficient buffer always means output buffer.
256 std::cerr << "Error during nnfw_session::run : " << e.what() << std::endl;
257 return NNFW_STATUS_INSUFFICIENT_OUTPUT_SIZE;
259 catch (const std::exception &e)
261 std::cerr << "Error during nnfw_session::run : " << e.what() << std::endl;
262 return NNFW_STATUS_ERROR;
265 _state = State::FINISHED_RUN;
266 return NNFW_STATUS_NO_ERROR;
269 NNFW_STATUS nnfw_session::run_async()
271 if (!isStatePreparedOrFinishedRun())
273 std::cerr << "Error during nnfw_session::run_async : "
274 << "run_async should be run after prepare" << std::endl;
275 return NNFW_STATUS_INVALID_STATE;
278 _execution->startExecute();
280 _state = State::RUNNING;
281 return NNFW_STATUS_NO_ERROR;
284 NNFW_STATUS nnfw_session::await()
286 if (!isStateRunning())
288 std::cerr << "Error during nnfw_session::run_await : "
289 << "run_await should be run after run_async" << std::endl;
290 return NNFW_STATUS_ERROR;
293 _execution->waitFinish();
295 _state = State::FINISHED_RUN;
296 return NNFW_STATUS_NO_ERROR;
299 NNFW_STATUS nnfw_session::set_input(uint32_t index, NNFW_TYPE /*type*/, const void *buffer,
302 if (!isStatePreparedOrFinishedRun())
304 std::cerr << "Error during nnfw_session::set_input : invalid state" << std::endl;
305 return NNFW_STATUS_INVALID_STATE;
308 if (!buffer && length != 0)
311 << "Error during nnfw_session::set_input : given buffer is NULL but the length is not 0"
313 return NNFW_STATUS_ERROR;
318 _execution->setInput(onert::ir::IOIndex(index), buffer, length);
320 catch (const std::exception &e)
322 std::cerr << "Error during nnfw_session::set_input : " << e.what() << std::endl;
323 return NNFW_STATUS_ERROR;
325 return NNFW_STATUS_NO_ERROR;
328 NNFW_STATUS nnfw_session::set_output(uint32_t index, NNFW_TYPE /*type*/, void *buffer,
331 if (!isStatePreparedOrFinishedRun())
333 std::cerr << "Error during nnfw_session::set_output : invalid state" << std::endl;
334 return NNFW_STATUS_INVALID_STATE;
337 if (!buffer && length != 0)
340 << "Error during nnfw_session::set_output : given buffer is NULL but the length is not 0"
342 return NNFW_STATUS_ERROR;
347 _execution->setOutput(onert::ir::IOIndex(index), buffer, length);
349 catch (const std::exception &e)
351 std::cerr << "Error during nnfw_session::set_output : " << e.what() << std::endl;
352 return NNFW_STATUS_ERROR;
354 return NNFW_STATUS_NO_ERROR;
357 NNFW_STATUS nnfw_session::input_size(uint32_t *number)
359 if (isStateInitialized()) // Model is not loaded
360 return NNFW_STATUS_INVALID_STATE;
364 if (number == nullptr)
366 std::cerr << "Error during nnfw_session::input_size, number is null pointer." << std::endl;
367 return NNFW_STATUS_UNEXPECTED_NULL;
369 *number = primary_subgraph()->getInputs().size();
371 catch (const std::exception &e)
373 std::cerr << "Error during nnfw_session::input_size : " << e.what() << std::endl;
374 return NNFW_STATUS_ERROR;
376 return NNFW_STATUS_NO_ERROR;
379 NNFW_STATUS nnfw_session::output_size(uint32_t *number)
381 if (isStateInitialized()) // Model is not loaded
382 return NNFW_STATUS_INVALID_STATE;
386 if (number == nullptr)
388 std::cerr << "Error during nnfw_session::output_size, number is null pointer." << std::endl;
389 return NNFW_STATUS_UNEXPECTED_NULL;
391 *number = primary_subgraph()->getOutputs().size();
393 catch (const std::exception &e)
395 std::cerr << "Error during nnfw_session::output_size" << e.what() << std::endl;
396 return NNFW_STATUS_ERROR;
398 return NNFW_STATUS_NO_ERROR;
401 NNFW_STATUS nnfw_session::set_input_layout(uint32_t index, NNFW_LAYOUT layout)
405 if (layout != NNFW_LAYOUT_NONE && layout != NNFW_LAYOUT_CHANNELS_FIRST &&
406 layout != NNFW_LAYOUT_CHANNELS_LAST)
408 std::cerr << "Error during nnfw_session::set_input_layout, not supported layout" << std::endl;
409 return NNFW_STATUS_ERROR;
411 _execution->setInputLayout(onert::ir::IOIndex(index), convertLayout(layout));
413 catch (const std::exception &e)
415 std::cerr << "Error during nnfw_session::set_input_layout : " << e.what() << std::endl;
416 return NNFW_STATUS_ERROR;
418 return NNFW_STATUS_NO_ERROR;
421 NNFW_STATUS nnfw_session::set_output_layout(uint32_t index, NNFW_LAYOUT layout)
425 if (layout != NNFW_LAYOUT_NONE && layout != NNFW_LAYOUT_CHANNELS_FIRST &&
426 layout != NNFW_LAYOUT_CHANNELS_LAST)
428 std::cerr << "Error during nnfw_session::set_output_layout, not supported layout"
430 return NNFW_STATUS_ERROR;
432 _execution->setOutputLayout(onert::ir::IOIndex(index), convertLayout(layout));
434 catch (const std::exception &e)
436 std::cerr << "Error during nnfw_session::set_output_layout : " << e.what() << std::endl;
437 return NNFW_STATUS_ERROR;
439 return NNFW_STATUS_NO_ERROR;
442 static NNFW_TYPE datatype_to_nnfw_dtype(onert::ir::DataType dt)
444 using onert::ir::DataType;
447 case DataType::FLOAT32:
448 return NNFW_TYPE_TENSOR_FLOAT32;
449 case DataType::INT32:
450 return NNFW_TYPE_TENSOR_INT32;
451 case DataType::QUANT_UINT8_ASYMM:
452 return NNFW_TYPE_TENSOR_QUANT8_ASYMM;
453 case DataType::BOOL8:
454 return NNFW_TYPE_TENSOR_BOOL;
455 case DataType::UINT8:
456 return NNFW_TYPE_TENSOR_UINT8;
457 case DataType::INT64:
458 return NNFW_TYPE_TENSOR_INT64;
459 case DataType::QUANT_INT8_ASYMM:
460 return NNFW_TYPE_TENSOR_QUANT8_ASYMM_SIGNED;
461 case DataType::UINT32:
462 case DataType::QUANT_INT8_SYMM:
464 throw std::runtime_error("Error: Model has type that runtime API does not support.");
468 NNFW_STATUS nnfw_session::apply_tensorinfo(uint32_t index, nnfw_tensorinfo ti)
472 if (isStateInitialized())
474 std::cerr << "Error during set_input_tensorinfo : should be run after load_model"
476 return NNFW_STATUS_INVALID_STATE;
479 if (ti.rank <= 0 || ti.rank > NNFW_MAX_RANK)
481 std::cerr << "unsupported rank: " << ti.rank << std::endl;
482 return NNFW_STATUS_ERROR;
485 for (int32_t i = 0; i < ti.rank; ++i)
489 std::cerr << "dim must be positive integer but was " << ti.dims[i] << std::endl;
490 return NNFW_STATUS_ERROR;
495 onert::ir::Shape new_shape(ti.rank);
496 for (int32_t i = 0; i < ti.rank; i++)
497 new_shape.dim(i) = ti.dims[i];
499 if (!isStatePreparedOrFinishedRun())
501 // In this case, if we apply input shape in primary_subgraph, it will propagate after
502 // compilation and excution
503 auto ind = primary_subgraph()->getInputs().at(index);
504 auto &input = primary_subgraph()->operands().at(ind);
506 // overwrite input shape with the shape from ti
507 input.info().shape(new_shape);
509 else // when called after nnfw_session::prepare()
511 _execution->changeInputShape(onert::ir::IOIndex(index), new_shape);
514 return NNFW_STATUS_NO_ERROR;
517 NNFW_STATUS nnfw_session::set_input_tensorinfo(uint32_t index, const nnfw_tensorinfo *ti)
519 nnfw_tensorinfo ti_copy = *ti;
520 return apply_tensorinfo(index, ti_copy);
523 NNFW_STATUS nnfw_session::input_tensorinfo(uint32_t index, nnfw_tensorinfo *ti)
525 if (isStateInitialized())
526 return NNFW_STATUS_INVALID_STATE;
532 std::cerr << "Error during nnfw_session::input_tensorinfo, tensorinfo is null pointer."
534 return NNFW_STATUS_UNEXPECTED_NULL;
536 if (index >= primary_subgraph()->getInputs().size())
538 std::cerr << "Error during nnfw_session::input_tensorinfo, index is out of range."
540 return NNFW_STATUS_ERROR;
542 auto opidx = primary_subgraph()->getInputs().at(index);
543 auto shape = primary_subgraph()->operands().at(opidx).shape();
544 if (isStatePreparedOrFinishedRun())
545 shape = _execution->getInputShape(onert::ir::IOIndex{index});
546 ti->rank = shape.rank();
547 for (int j = 0; j < ti->rank; ++j)
549 ti->dims[j] = shape.dim(j);
551 ti->dtype = datatype_to_nnfw_dtype(primary_subgraph()->operands().at(opidx).typeInfo().type());
553 catch (const std::exception &e)
555 std::cerr << "Error during nnfw_session::input_tensorinfo : " << e.what() << std::endl;
556 return NNFW_STATUS_ERROR;
558 return NNFW_STATUS_NO_ERROR;
561 NNFW_STATUS nnfw_session::output_tensorinfo(uint32_t index, nnfw_tensorinfo *ti)
563 if (isStateInitialized())
564 return NNFW_STATUS_INVALID_STATE;
568 std::cerr << "Error during nnfw_session::output_tensorinfo, tensorinfo is null pointer."
570 return NNFW_STATUS_UNEXPECTED_NULL;
573 if (index >= primary_subgraph()->getOutputs().size())
575 std::cerr << "Error during nnfw_session::output_tensorinfo, index is out of range."
577 return NNFW_STATUS_ERROR;
582 auto opidx = primary_subgraph()->getOutputs().at(index);
583 auto shape = primary_subgraph()->operands().at(opidx).shape();
584 // If it is called after `nnfw_run` then get the shape from Execution, not from the graph
585 if (isStateFinishedRun())
586 shape = _execution->getOutputShape(onert::ir::IOIndex{index});
587 ti->rank = shape.rank();
588 for (int j = 0; j < ti->rank; ++j)
590 ti->dims[j] = shape.dim(j);
592 ti->dtype = datatype_to_nnfw_dtype(primary_subgraph()->operands().at(opidx).typeInfo().type());
594 catch (const std::exception &e)
596 std::cerr << "Error during nnfw_session::output_tensorinfo : " << e.what() << std::endl;
597 return NNFW_STATUS_ERROR;
600 return NNFW_STATUS_NO_ERROR;
602 NNFW_STATUS nnfw_session::register_custom_operation(const std::string &id,
603 nnfw_custom_eval eval_func)
605 _kernel_registry->registerKernel(id, eval_func);
606 return NNFW_STATUS_NO_ERROR;
609 static std::string get_op_backend_string(std::string op)
611 #define MAP_MACRO(CircleName, OneRTName) {#CircleName, #OneRTName},
613 static std::unordered_map<std::string, std::string> operation_map = {
619 auto n = operation_map.find(op);
621 if (n == operation_map.end())
623 // this return value is handled by a caller to return error code
624 return std::string("");
632 NNFW_STATUS nnfw_session::set_available_backends(const char *backends)
634 if (!isStateModelLoaded())
635 return NNFW_STATUS_INVALID_STATE;
640 return NNFW_STATUS_UNEXPECTED_NULL;
641 if (null_terminating(backends, MAX_BACKEND_NAME_LENGTH) == false)
642 return NNFW_STATUS_ERROR;
644 auto &options = _compiler->options();
646 using namespace onert::util;
648 options.backend_list = nnfw::misc::split(std::string{backends}, ';');
650 catch (const std::exception &e)
652 std::cerr << "Error during nnfw_session::set_available_backends : " << e.what() << std::endl;
653 return NNFW_STATUS_ERROR;
655 return NNFW_STATUS_NO_ERROR;
658 NNFW_STATUS nnfw_session::set_op_backend(const char *op, const char *backend)
660 if (!isStateModelLoaded())
661 return NNFW_STATUS_INVALID_STATE;
666 return NNFW_STATUS_UNEXPECTED_NULL;
667 if (!null_terminating(op, MAX_OP_NAME_LENGTH) ||
668 !null_terminating(backend, MAX_BACKEND_NAME_LENGTH))
669 return NNFW_STATUS_ERROR;
671 auto key = get_op_backend_string(op);
675 return NNFW_STATUS_ERROR;
678 auto &opcode_to_backend = _compiler->options().manual_scheduler_options.opcode_to_backend;
679 opcode_to_backend.emplace(onert::ir::toOpCode(key), backend);
681 catch (const std::exception &e)
683 std::cerr << "Error during nnfw_session::set_op_backend : " << e.what() << std::endl;
684 return NNFW_STATUS_ERROR;
686 return NNFW_STATUS_NO_ERROR;
689 NNFW_STATUS nnfw_session::set_config(const char *key, const char *value)
691 if (!isStateModelLoaded())
692 return NNFW_STATUS_INVALID_STATE;
695 return NNFW_STATUS_UNEXPECTED_NULL;
697 auto &options = _compiler->options();
699 using namespace onert::util;
701 const std::string skey = key;
703 if (skey == config::TRACE_FILEPATH)
705 options.trace_filepath = value;
707 else if (skey == config::GRAPH_DOT_DUMP)
709 options.graph_dump_level = toInt(value);
711 else if (skey == config::OP_SEQ_MAX_NODE)
713 options.op_seq_max_node = toInt(value);
715 else if (skey == config::EXECUTOR)
717 options.executor = value;
719 else if (skey == config::OP_BACKEND_ALLOPS)
721 options.manual_scheduler_options.backend_for_all = value;
723 else if (skey == config::USE_SCHEDULER)
725 options.he_scheduler = toBool(value);
727 else if (skey == config::PROFILING_MODE)
729 options.he_profiling_mode = toBool(value);
731 else if (skey == config::DISABLE_COMPILE)
733 options.disable_compile = toBool(value);
737 return NNFW_STATUS_ERROR;
739 return NNFW_STATUS_NO_ERROR;
742 onert::ir::Graph *nnfw_session::primary_subgraph()
747 return _subgraphs->primary().get();
752 // TODO Remove const_cast
753 // We assumed the graph will not change after compilation, but shape could change
754 return const_cast<onert::ir::Graph *>(&_execution->primary_subgraph());
758 NNFW_STATUS nnfw_session::get_config(const char *key, char *value, size_t value_size)
760 if (!isStateModelLoaded())
761 return NNFW_STATUS_INVALID_STATE;
764 return NNFW_STATUS_UNEXPECTED_NULL;
766 auto &options = _compiler->options();
768 auto check_boundary = [](size_t dest_size, std::string &src) {
769 if (dest_size < src.length() + 1 /* for '\0' */)
771 std::cerr << "buffer is small to copy config value." << std::endl;
777 const std::string skey = key;
779 if (skey == onert::util::config::BACKENDS)
781 if (options.backend_list.size() == 0)
782 return NNFW_STATUS_NO_ERROR; // no setting backend is not an error of get_config_str()
784 auto str = nnfw::misc::join(options.backend_list.begin(), options.backend_list.end(), ";");
786 if (!check_boundary(value_size, str))
787 return NNFW_STATUS_ERROR;
789 strncpy(value, str.c_str(), value_size);
791 else if (skey == onert::util::config::EXECUTOR)
793 if (!check_boundary(value_size, options.executor))
794 return NNFW_STATUS_ERROR;
796 strncpy(value, options.executor.c_str(), options.executor.length());
800 return NNFW_STATUS_ERROR;
803 return NNFW_STATUS_NO_ERROR;
806 bool nnfw_session::isStateInitialized()
808 if (_state == State::INITIALIZED)
821 bool nnfw_session::isStateModelLoaded()
823 if (_state == State::MODEL_LOADED)
828 assert(!primary_subgraph()->isBuildingPhase());
837 bool nnfw_session::isStatePrepared()
839 if (_state == State::PREPARED)
844 assert(!primary_subgraph()->isBuildingPhase());
853 bool nnfw_session::isStateRunning()
855 if (_state == State::RUNNING)
860 assert(!primary_subgraph()->isBuildingPhase());
866 bool nnfw_session::isStateFinishedRun()
868 if (_state == State::FINISHED_RUN)
873 assert(!primary_subgraph()->isBuildingPhase());
882 bool nnfw_session::isStatePreparedOrFinishedRun()
884 return isStatePrepared() || isStateFinishedRun();
887 NNFW_STATUS nnfw_session::input_tensorindex(const char *tensorname, uint32_t *index)
889 return getTensorIndexImpl(*primary_subgraph(), tensorname, index, true);
892 NNFW_STATUS nnfw_session::output_tensorindex(const char *tensorname, uint32_t *index)
894 return getTensorIndexImpl(*primary_subgraph(), tensorname, index, false);