| 333 | } |
| 334 | |
| 335 | Offset<Vector<Offset<Operator>>> ExportOperators( |
| 336 | const Model& model, |
| 337 | const std::map<OperatorType, std::unique_ptr<BaseOperator>>& ops_by_type, |
| 338 | const details::OperatorsMap& operators_map, |
| 339 | const details::TensorsMap& tensors_map, FlatBufferBuilder* builder, |
| 340 | std::set<int32_t>* variable_tensor_indices, const ExportParams& params) { |
| 341 | variable_tensor_indices->clear(); |
| 342 | |
| 343 | auto is_tflite_builtin = [](const BaseOperator* op) { |
| 344 | const auto& tflite_builtins = GetBuiltinOpsMap(); |
| 345 | return (op && tflite_builtins.find(op->name()) != tflite_builtins.end()); |
| 346 | }; |
| 347 | |
| 348 | // The operators are in execution order, so we just follow tf.mini order. |
| 349 | std::vector<Offset<Operator>> op_vector; |
| 350 | for (const auto& op : model.operators) { |
| 351 | std::vector<int32_t> inputs; |
| 352 | for (const string& input : op->inputs) { |
| 353 | // -1 is the ID for optional tensor in TFLite output |
| 354 | int id = model.IsOptionalArray(input) ? -1 : tensors_map.at(input); |
| 355 | inputs.push_back(id); |
| 356 | } |
| 357 | std::vector<int32_t> outputs; |
| 358 | for (const string& output : op->outputs) { |
| 359 | outputs.push_back(tensors_map.at(output)); |
| 360 | } |
| 361 | const toco::OperatorSignature op_signature = {op.get(), &model}; |
| 362 | const auto key = details::OperatorKey(op_signature, ops_by_type, |
| 363 | params.enable_select_tf_ops); |
| 364 | int op_index = operators_map.at(key); |
| 365 | |
| 366 | auto tflite_op_it = ops_by_type.find(op->type); |
| 367 | BaseOperator* tflite_op = tflite_op_it == ops_by_type.end() |
| 368 | ? nullptr |
| 369 | : tflite_op_it->second.get(); |
| 370 | |
| 371 | // This is a custom op unless we can find it in ops_by_type, and even then |
| 372 | // it could be a custom op (such as kUnsupported). |
| 373 | auto options = Options::Custom(0); |
| 374 | |
| 375 | std::vector<bool> mutating_input_variables; |
| 376 | |
| 377 | // It is conceivable that an op is exportable via Serialize() but does not |
| 378 | // have a corresponding TFLITE builtin. In that case, when flex mode is |
| 379 | // enabled we should export it as a flex op, not as a native. |
| 380 | bool export_as_flex_op = !is_tflite_builtin(tflite_op) && |
| 381 | key.is_flex_op() && |
| 382 | !op->tensorflow_node_def.empty(); |
| 383 | if (export_as_flex_op) { |
| 384 | auto fbb = WriteFlexOpOptions(op->tensorflow_node_def); |
| 385 | if (fbb) { |
| 386 | options = Options::Custom(builder->CreateVector(fbb->GetBuffer())); |
| 387 | } |
| 388 | } else if (tflite_op) { |
| 389 | options = tflite_op->Serialize(*op, builder); |
| 390 | mutating_input_variables = tflite_op->GetMutatingInputVariables(*op); |
| 391 | |
| 392 | if (!mutating_input_variables.empty()) { |
no test coverage detected