text: #include "caffe2/core/net.h"
  #include "caffe2/core/net_simple.h"
  
  #include <set>
  #include <unordered_map>
  #include <unordered_set>
  
  #include "caffe2/core/init.h"
  #include "caffe2/core/operator.h"
  #include "caffe2/core/timer.h"
  #include "caffe2/proto/caffe2_pb.h"
  #include "caffe2/utils/proto_utils.h"
  #include "caffe2/utils/string_utils.h"
  
  C10_DEFINE_string(
      caffe2_override_executor,
      "",
      "Comma-separated list of executor overrides");
  
  namespace caffe2 {
  
  C10_DEFINE_REGISTRY(
      NetRegistry,
      NetBase,
      const std::shared_ptr<const NetDef>&,
      Workspace*);
  
  NetBase::NetBase(
      const std::shared_ptr<const NetDef>& def,
      Workspace* /* unused */)
      : external_input_(
            def->external_input().begin(),
            def->external_input().end()),
        external_output_(
            def->external_output().begin(),
            def->external_output().end()),
        name_(def->name()),
        net_def_(def) {
    static GlobalInitIsCalledGuard guard;
    C10_LOG_API_USAGE_ONCE("caffe2.net.create");
    // Check that node_name is empty for all ops
    for (const OperatorDef& op : def->op()) {
      if (op.has_device_option()) {
        CAFFE_ENFORCE(
            !op.device_option().has_node_name(),
            "node_name must be empty for all operators at execution time.");
      }
    }
  
    // Go through the operators and make sure that blobs are correctly made.
    std::set<string> known_blobs(
        external_input_.begin(), external_input_.end());
    std::set<string> remaining_output(
        external_output_.begin(), external_output_.end());
    for (const auto& blob : known_blobs) {
      remaining_output.erase(blob);
    }
    for (const OperatorDef& op : def->op()) {
      for (const string& in : op.input()) {
        if (!known_blobs.count(in)) {
          if (external_input_.size()) {
            CAFFE_THROW(
                "op ",
                op.type(),
                ": Source for input ",
                in,
                " is unknown for net ",
                def->name(),
                ", operator ",
                ProtoDebugString(op));
          } else {
            // If we are not declaring input and output, we will simply VLOG it
            // for debugging purposes.
            VLOG(1) << "op " << op.type() << ": input " << in << " is unknown.";
          }
        }
      }
      for (const string& out : op.output()) {
        known_blobs.insert(out);
        remaining_output.erase(out);
      }
    }
    // Finally, check if all declared outputs are being created.
    CAFFE_ENFORCE(
        remaining_output.size() == 0,
        "Some of the blobs are declared as output but never produced by the "
        "net ",
        def->name(),
        ", the first one is ",
        *remaining_output.begin());
  }
  
  bool NetBase::RunAsync() {
    for (auto& op : GetOperators()) {
      op->ResetEvent();
    }
    return DoRunAsync();
  }
  
  void NetBase::Cancel() {
    for (auto& op : GetOperators()) {
      op->Cancel();
    }
  }
  
  namespace {
  const std::string kSimpleNet = "simple";
  
  std::vector<NetObserverCreator>* GetNetObserverCreators() {
    static std::vector<NetObserverCreator> creators;
    return &creators;
  }
  
  const std::unordered_map<std::string, std::string>& defaultOverrides() {
    // redirecting legacy net types to async_scheduling (except for 'simple');
    // async_scheduling checks net type for backward compatibility
    static const std::unordered_map<std::string, std::string> overrides = {
        {"dag", "async_scheduling"},
        {"prof_dag", "async_scheduling"},
        {"async_dag", "async_scheduling"},
        {"async_polling", "async_scheduling"},
        {"async_simple", "simple"}, // "async_simple" impl has been removed.
        {"rnn", "simple"}, // "rnn" impl has been removed.
    };
    return overrides;
  }
  
  void ApplyPotentialExecutorOverride(std::string* net_type) {
    auto executors = caffe2::split(',', FLAGS_caffe2_override_executor);
    CAFFE_ENFORCE(
        executors.size() % 2 == 0, "Invalid override executors flag value");
    std::unordered_map<std::string, std::string> overrides;
    for (const auto& kv : defaultOverrides()) {
      overrides[kv.first] = kv.second;
    }
    for (size_t idx = 0; idx < executors.size(); idx += 2) {
      overrides[executors[idx]] = executors[idx + 1];
    }
    if (overrides.count(*net_type)) {
      VLOG(1) << "Overrode net type '" << *net_type << "' with '"
              << overrides[*net_type] << "'";
      *net_type = overrides[*net_type];
    }
  }
  
  } // namespace
  
  void AddGlobalNetObserverCreator(NetObserverCreator creator) {
    GetNetObserverCreators()->push_back(creator);
    VLOG(1) << "Have set a custom GlobalNetObserverCreator";
  }
  
  void ClearGlobalNetObservers() {
    GetNetObserverCreators()->clear();
    VLOG(1) << "All net observers cleared";
  }
  
  unique_ptr<NetBase> CreateNet(const NetDef& net_def, Workspace* ws) {
    std::shared_ptr<NetDef> tmp_net_def(new NetDef(net_def));
    return CreateNet(tmp_net_def, ws);
  }
  
  unique_ptr<NetBase> CreateNet(
      const std::shared_ptr<const NetDef>& net_def,
      Workspace* ws) {
    std::string net_type;
    if (net_def->has_type() && !net_def->type().empty()) {
      net_type = net_def->type();
    } else {
      // By default, we will return a simple network that just runs all operators
      // sequentially.
      net_type = kSimpleNet;
    }
    ApplyPotentialExecutorOverride(&net_type);
    unique_ptr<NetBase> net = NetRegistry()->Create(net_type, net_def, ws);
  
    VLOG(1) << "Adding a global observer to a net";
    if (net) {
      auto* observer_creators = GetNetObserverCreators();
      for (auto& creator : *observer_creators) {
        net->AttachObserver(creator(net.get()));
      }
    }
    return net;
  }
  
  TaskThreadPoolBase* ExecutorHelper::GetPool(
      const DeviceOption& /* unused */) const {
    CAFFE_THROW("Not implemented");
  }
  
  std::vector<OperatorBase*> ExecutorHelper::GetOperators() const {
    CAFFE_THROW("Not implemented");
  }
  
  int ExecutorHelper::GetNumWorkers() const {
    CAFFE_THROW("Not implemented");
  }
  
  // benchmark an individual run so that we can FeedBlobs with new inputs
  // no warmup
  // return time taken in microseconds
  float NetBase::TEST_Benchmark_One_Run() {
    Timer timer;
    CAFFE_ENFORCE(Run(), "Run has failed.");
    return timer.MicroSeconds();
  }
  
  std::vector<float> NetBase::TEST_Benchmark(
      const int warmup_runs,
      const int main_runs,
      const bool run_individual) {
    LOG(INFO) << "Starting benchmark, running warmup runs";
    CAFFE_ENFORCE(
        warmup_runs >= 0,
        "Number of warm up runs should be non negative, provided ",
        warmup_runs);
    for (int run_idx = 0; run_idx < warmup_runs; ++run_idx) {
      CAFFE_ENFORCE(Run(), "Warmup run ", run_idx, " has failed");
    }
  
    LOG(INFO) << "Running main runs";
    CAFFE_ENFORCE(
        main_runs >= 0,
        "Number of main runs should be non negative, provided ",
        main_runs);
  
    Timer timer;
    for (int run_idx = 0; run_idx < main_runs; ++run_idx) {
      CAFFE_ENFORCE(Run(), "Main run ", run_idx, " has failed");
    }
    auto millis = timer.MilliSeconds();
    LOG(INFO) << "Main runs finished. Milliseconds per iter: "
              // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
              << millis / main_runs
              << ". Iters per second: " << 1000.0 * main_runs / millis;
  
    if (run_individual) {
      LOG(INFO) << "Net does not support per-op benchmark; "
                   "to run it, switch to a simple net type";
    }
    // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
    return std::vector<float>{millis / main_runs};
  }
  
  } // namespace caffe2
  