/**
 * Copyright (c) 2026 Huawei Technologies Co., Ltd. All Rights Reserved.
 * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
 * CANN Open Software License Agreement Version 2.0 (the "License").
 * Please refer to the License for details. You may not use this file except in compliance with the License.
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
 * See LICENSE in the root of the software repository for the full text of the License.
 */

#include <pthread.h>

#include "tf_adapter/common/common.h"
#include "tf_adapter/util/npu_attrs.h"
#include "om_executor.h"

namespace tensorflow {
ModelProcess::ModelProcess(const std::string &model_data) {
  model_data_ = model_data;
  StartWorkThread();
}

ModelProcess::~ModelProcess() {
  DestroyResource();
  run_flag_ = false;
  ADP_LOG(INFO) << "send request to thread.";
  {
    std::unique_lock<std::mutex> lk{mu_request_};
    request_flag_ = true;
    cond_request_.notify_one();
  }
  ADP_LOG(INFO) << "start join.";
  if (work_thread_.joinable()) {
    work_thread_.join();
  }
  ADP_LOG(INFO) << "join success.";
}

void ModelProcess::DestroyResource() {
  UnloadModel();
  DestroyInput();
  DestroyOutput();
  if (is_set_device_) {
    aclrtResetDevice(device_id_);
    is_set_device_ = false;
  }
}

Status ModelProcess::PrepareProcess() {
  TF_RETURN_IF_ERROR(LoadModelFromFile());
  TF_RETURN_IF_ERROR(CreateInput());
  TF_RETURN_IF_ERROR(CreateOutput());
  return Status::OK();
}

bool ModelProcess::IsDynamic(const aclmdlIODims &dims) const {
  for (size_t i = 0; i < dims.dimCount; ++i) {
    if ((dims.dims[i] == -1) || (dims.dims[i] == -2)) {
      return true;
    }
  }
  return false;
}

Status ModelProcess::GetDynamicGearInfo() {
  aclmdlBatch dynamic_batch = {};
  REQUIRES_ACL_STATUS_OK(aclmdlGetDynamicBatch(model_desc_, &dynamic_batch), aclmdlGetDynamicBatch);
  if (dynamic_batch.batchCount > 0U) {
    dymainc_gear_type_ = DynamicGearType::DYNAMIC_BATCH;
    for (size_t i = 0U; i < dynamic_batch.batchCount; ++i) {
      std::vector<uint64_t> current_batch;
      current_batch.emplace_back(dynamic_batch.batch[i]);
      ADP_LOG(INFO) << "this " << i << " batch is " << dynamic_batch.batch[i];
      dynamic_gear_info_.emplace_back(current_batch);
    }
    for (size_t j = 0U; j < is_input_dynamic_.size(); ++j) {
      if (is_input_dynamic_[j]) {
        // the first dynamic inut is enough
        dynamic_gear_input_index_ = j;
        break;
      }
    }
    ADP_LOG(INFO) << "dynamic gear input index is " << dynamic_gear_input_index_;
    aclmdlIODims dims = {};
    REQUIRES_ACL_STATUS_OK(aclmdlGetInputDims(model_desc_, dynamic_gear_input_index_, &dims), aclmdlGetInputDims);
    for (size_t k = 0U; k < dims.dimCount; ++k) {
      if (dims.dims[k] == -1) {
        ADP_LOG(INFO) << "dynamic gear shape index is " << k;
        dynamic_gear_shape_index_.emplace_back(k);
        break;
      }
    }
    if (dynamic_gear_shape_index_.size() != 1U) {
      ADP_LOG(ERROR) << "dynamic_gear_shape_index_ size is invalid, its value is " << dynamic_gear_shape_index_.size();
      return tensorflow::errors::Internal("get dynamic gear shape index fail");
    }
    return Status::OK();
  }
  return Status::OK();
}

Status ModelProcess::LoadModelFromFile() {
  const aclError acl_ret = aclInit(nullptr);
  if ((acl_ret != ACL_SUCCESS) && (acl_ret != ACL_ERROR_REPEAT_INITIALIZE)) {
    return tensorflow::errors::Internal("aclInit fail");
  }
  (void)GetEnvDeviceID(device_id_);
  REQUIRES_ACL_STATUS_OK(aclrtSetDevice(device_id_), aclrtSetDevice);
  is_set_device_ = true;
  REQUIRES_ACL_STATUS_OK(aclmdlLoadFromMem(model_data_.c_str(), model_data_.size(), &model_id_), aclmdlLoadFromMem);
  load_flag_ = true;
  model_desc_ = aclmdlCreateDesc();
  REQUIRES_NOT_NULL(model_desc_);
  REQUIRES_ACL_STATUS_OK(aclmdlGetDesc(model_desc_, model_id_), aclmdlGetDesc);
  aclmdlIODims dims = {};
  for (size_t i = 0U; i < aclmdlGetNumInputs(model_desc_); ++i) {
    REQUIRES_ACL_STATUS_OK(aclmdlGetInputDims(model_desc_, i, &dims), aclmdlGetInputDims);
    is_input_dynamic_.emplace_back(IsDynamic(dims));
    ADP_LOG(INFO) << "this " << i << " input is " << is_input_dynamic_[i];
  }
  for (size_t j = 0U; j < aclmdlGetNumOutputs(model_desc_); ++j) {
    REQUIRES_ACL_STATUS_OK(aclmdlGetOutputDims(model_desc_, j, &dims), aclmdlGetOutputDims);
    is_output_dynamic_.emplace_back(IsDynamic(dims));
    ADP_LOG(INFO) << "this " << j << " output is " << is_output_dynamic_[j];
  }
  TF_RETURN_IF_ERROR(GetDynamicGearInfo());
  return Status::OK();
}

Status ModelProcess::CreateInput() {
  input_ = aclmdlCreateDataset();
  REQUIRES_NOT_NULL(input_);
  size_t input_num = aclmdlGetNumInputs(model_desc_);
  for (size_t i = 0U; i < input_num; ++i) {
    size_t input_size = aclmdlGetInputSizeByIndex(model_desc_, i);
    ADP_LOG(INFO) << "this " << i << " input size is " << input_size;
    void *dev_ptr = nullptr;
    if (input_size > 0U) {
      REQUIRES_ACL_STATUS_OK(aclrtMalloc(&dev_ptr, input_size, ACL_MEM_MALLOC_NORMAL_ONLY), aclrtMalloc);
      REQUIRES_NOT_NULL(dev_ptr);
    }
    aclDataBuffer *data_buf = aclCreateDataBuffer(dev_ptr, input_size);
    if (data_buf == nullptr) {
      (void)aclrtFree(dev_ptr);
      return tensorflow::errors::Internal("aclCreateDataBuffer fail");
    }
    if (aclmdlAddDatasetBuffer(input_, data_buf) != ACL_SUCCESS) {
      (void)aclrtFree(dev_ptr);
      (void)aclDestroyDataBuffer(data_buf);
      return tensorflow::errors::Internal("aclmdlAddDatasetBuffer fail");
    }
  }
  return Status::OK();
}

Status ModelProcess::CreateOutput() {
  output_ = aclmdlCreateDataset();
  REQUIRES_NOT_NULL(output_);
  size_t output_num = aclmdlGetNumOutputs(model_desc_);
  for (size_t i = 0U; i < output_num; ++i) {
    size_t output_size = aclmdlGetOutputSizeByIndex(model_desc_, i);
    ADP_LOG(INFO) << "this " << i << " output size is " << output_size;
    void *dev_ptr = nullptr;
    if (output_size > 0U) {
      REQUIRES_ACL_STATUS_OK(aclrtMalloc(&dev_ptr, output_size, ACL_MEM_MALLOC_NORMAL_ONLY), aclrtMalloc);
      REQUIRES_NOT_NULL(dev_ptr);
    }
    outputs_feed_nullptr_vec_.emplace_back(dev_ptr == nullptr);
    aclDataBuffer *data_buf = aclCreateDataBuffer(dev_ptr, output_size);
    if (data_buf == nullptr) {
      (void)aclrtFree(dev_ptr);
      return tensorflow::errors::Internal("aclCreateDataBuffer fail");
    }
    if (aclmdlAddDatasetBuffer(output_, data_buf) != ACL_SUCCESS) {
      (void)aclrtFree(dev_ptr);
      (void)aclDestroyDataBuffer(data_buf);
      return tensorflow::errors::Internal("aclmdlAddDatasetBuffer fail");
    }
  }
  return Status::OK();
}

Status ModelProcess::Execute(const std::vector<Tensor> &inputs, std::vector<Tensor> &outputs) {
  TF_RETURN_IF_ERROR(ProcessInput(inputs));
  REQUIRES_ACL_STATUS_OK(aclmdlExecute(model_id_, input_, output_), aclmdlExecute);
  TF_RETURN_IF_ERROR(ProcessOutput(outputs));
  return Status::OK();
}

Status ModelProcess::ProcessDynamicGearInput(const std::vector<Tensor> &inputs) const {
  if (dymainc_gear_type_ == DynamicGearType::DYNAMIC_UNDEFINED) {
    return Status::OK();
  }
  if (dymainc_gear_type_ == DynamicGearType::DYNAMIC_BATCH) {
    if (inputs.size() <= dynamic_gear_input_index_) {
      ADP_LOG(ERROR) << "input size " << inputs.size() << " is invalid, need be larger than "
                     << dynamic_gear_input_index_;
      return tensorflow::errors::Internal("input size %zu is invalid, need be larger than %zu", inputs.size(),
                                          dynamic_gear_input_index_);
    }
    auto dims = inputs[dynamic_gear_input_index_].shape().dim_sizes();
    if (dims.size() <= dynamic_gear_shape_index_[0]) {
      ADP_LOG(ERROR) << "input dim size " << dims.size() << " is invalid, need be larger than "
                     << dynamic_gear_shape_index_[0];
      return tensorflow::errors::Internal("input dim size %zu is invalid, need be larger than %zu", dims.size(),
                                          dynamic_gear_shape_index_[0]);
    }
    const auto current_batch = dims[dynamic_gear_shape_index_[0]];
    size_t dynamic_batch_index = 0U;
    REQUIRES_ACL_STATUS_OK(aclmdlGetInputIndexByName(model_desc_, ACL_DYNAMIC_TENSOR_NAME, &dynamic_batch_index),
                           aclmdlGetInputIndexByName);
    ADP_LOG(INFO) << "current batch is " << current_batch << ", shape index is " << dynamic_gear_shape_index_[0]
                  << ", dynamic_batch_index is " << dynamic_batch_index;
    REQUIRES_ACL_STATUS_OK(aclmdlSetDynamicBatchSize(model_id_, input_, dynamic_batch_index, current_batch),
                           aclmdlSetDynamicBatchSize);
    return Status::OK();
  }
  return Status::OK();
}

Status ModelProcess::ProcessInput(const std::vector<Tensor> &inputs) const {
  const size_t model_input_size = aclmdlGetNumInputs(model_desc_);
  if (dymainc_gear_type_ == DynamicGearType::DYNAMIC_UNDEFINED) {
    if (inputs.size() < model_input_size) {
      ADP_LOG(ERROR) << "input num " << inputs.size() << " is smaller than model input num " << model_input_size;
      return tensorflow::errors::Internal("input num is invalid");
    }
  }
  // dynamic gear data need to be feeded alone
  size_t need_feed_input_cnts = ((dymainc_gear_type_ == DynamicGearType::DYNAMIC_UNDEFINED) && (model_input_size >= 1U))
                                    ? model_input_size
                                    : (model_input_size - 1U);
  for (size_t i = 0U; i < need_feed_input_cnts; ++i) {
    auto tensor_data = inputs[i].tensor_data().data();
    auto tensor_size = inputs[i].tensor_data().size();
    aclDataBuffer *data_buf = aclmdlGetDatasetBuffer(input_, i);
    REQUIRES_NOT_NULL(data_buf);
    void *dev_ptr = aclGetDataBufferAddr(data_buf);
    size_t cur_size = aclGetDataBufferSizeV2(data_buf);
    ADP_LOG(INFO) << "current input tensor is " << inputs[i].DebugString() << " model cur size is " << cur_size;
    if (tensor_size > cur_size) {  // dynamic input maybe larger than last, free and malloc larger size
      if (dev_ptr != nullptr) {    // only not nullptr need to be free, first infer maybe nullptr and 0 size
        (void)aclrtFree(dev_ptr);
      }
      REQUIRES_ACL_STATUS_OK(aclrtMalloc(&dev_ptr, tensor_size, ACL_MEM_MALLOC_NORMAL_ONLY), aclrtMalloc);
      (void)aclUpdateDataBuffer(data_buf, dev_ptr, tensor_size);
    }
    REQUIRES_NOT_NULL(dev_ptr);
    REQUIRES_ACL_STATUS_OK(aclrtMemcpy(dev_ptr, tensor_size, tensor_data, tensor_size, ACL_MEMCPY_HOST_TO_DEVICE),
                           aclrtMemcpy);
    // set shpae
    tensorflow::DataType tf_type = inputs[i].dtype();
    aclDataType acl_dt = ACL_DT_UNDEFINED;
    TF_RETURN_IF_ERROR(MappingTfDtToAcl(tf_type, acl_dt));
    auto dims = inputs[i].shape().dim_sizes();
    aclTensorDesc *tensor_desc = aclCreateTensorDesc(
        acl_dt, dims.size(), (dims.empty() ? nullptr : reinterpret_cast<const int64_t *>(dims.data())),
        ACL_FORMAT_UNDEFINED);
    REQUIRES_NOT_NULL(tensor_desc);
    const aclError ret = aclmdlSetDatasetTensorDesc(input_, tensor_desc, i);
    aclDestroyTensorDesc(tensor_desc);
    tensor_desc = nullptr;
    REQUIRES_ACL_STATUS_OK(ret, aclmdlSetDatasetTensorDesc);
  }
  TF_RETURN_IF_ERROR(ProcessDynamicGearInput(inputs));
  return Status::OK();
}

Status ModelProcess::ProcessStaticOutput(const size_t index, const tensorflow::DataType tf_type,
                                         const aclDataBuffer *data_buf, std::vector<Tensor> &outputs) const {
  ADP_LOG(INFO) << "this out " << index << " is static.";
  void *dev_ptr = aclGetDataBufferAddr(data_buf);
  REQUIRES_NOT_NULL(dev_ptr);
  aclmdlIODims acl_dims = {};
  if (dymainc_gear_type_ == DynamicGearType::DYNAMIC_UNDEFINED) {
    REQUIRES_ACL_STATUS_OK(aclmdlGetOutputDims(model_desc_, index, &acl_dims), aclmdlGetOutputDims);
  } else {
    REQUIRES_ACL_STATUS_OK(aclmdlGetCurOutputDims(model_desc_, index, &acl_dims), aclmdlGetCurOutputDims);
  }
  TensorShape tf_shape;
  for (size_t j = 0U; j < acl_dims.dimCount; ++j) {
    tf_shape.AddDim(acl_dims.dims[j]);
  }
  Tensor tensor = Tensor(tf_type, tf_shape);
  auto tensor_data = const_cast<char *>(tensor.tensor_data().data());
  auto tensor_size = tensor.tensor_data().size();
  ADP_LOG(INFO) << "current output " << index << ", tensor is " << tensor.DebugString();
  REQUIRES_ACL_STATUS_OK(aclrtMemcpy(tensor_data, tensor_size, dev_ptr, tensor_size, ACL_MEMCPY_DEVICE_TO_HOST),
                         aclrtMemcpy);
  outputs.emplace_back(std::move(tensor));
  return Status::OK();
}

Status ModelProcess::ProcessDynamicOutput(const size_t index, const tensorflow::DataType tf_type,
                                          aclDataBuffer *data_buf, std::vector<Tensor> &outputs) const {
  ADP_LOG(INFO) << "this out " << index << " is dynamic.";
  void *dev_ptr = aclGetDataBufferAddr(data_buf);
  REQUIRES_NOT_NULL(dev_ptr);
  auto *desc = aclmdlGetDatasetTensorDesc(output_, index);
  REQUIRES_NOT_NULL(desc);
  size_t real_size = aclGetTensorDescSize(desc);
  TensorShape tf_shape;
  size_t shape_size = aclGetTensorDescNumDims(desc);
  ADP_LOG(INFO) << "get model output size is " << real_size << ", shape size is " << shape_size << " dt is " << tf_type;
  int64_t cur_dim = 0;
  for (size_t j = 0U; j < shape_size; ++j) {
    REQUIRES_ACL_STATUS_OK(aclGetTensorDescDimV2(desc, j, &cur_dim), aclGetTensorDescDimV2);
    tf_shape.AddDim(cur_dim);
  }
  Tensor tensor = Tensor(tf_type, tf_shape);
  auto tensor_data = const_cast<char *>(tensor.tensor_data().data());
  auto tensor_size = tensor.tensor_data().size();
  REQUIRES_ACL_STATUS_OK(aclrtMemcpy(tensor_data, tensor_size, dev_ptr, tensor_size, ACL_MEMCPY_DEVICE_TO_HOST),
                         aclrtMemcpy);
  ADP_LOG(INFO) << "current output " << index << " tensor is " << tensor.DebugString();
  outputs.emplace_back(std::move(tensor));
  if (outputs_feed_nullptr_vec_[index]) {
    ADP_LOG(INFO) << "this output " << index << " need update nullptr.";
    (void)aclrtFree(dev_ptr);
    (void)aclUpdateDataBuffer(data_buf, nullptr, 0);
  }
  return Status::OK();
}

Status ModelProcess::ProcessOutput(std::vector<Tensor> &outputs) {
  outputs.clear();
  for (size_t i = 0U; i < aclmdlGetNumOutputs(model_desc_); ++i) {
    aclDataBuffer *data_buf = aclmdlGetDatasetBuffer(output_, i);
    REQUIRES_NOT_NULL(data_buf);
    aclDataType acl_dt = aclmdlGetOutputDataType(model_desc_, i);
    tensorflow::DataType tf_type = DT_FLOAT;
    TF_RETURN_IF_ERROR(MappingAclDtToTf(acl_dt, tf_type));
    ADP_LOG(INFO) << "model output " << i << " dt is " << tf_type;
    if (!is_output_dynamic_[i]) {
      TF_RETURN_IF_ERROR(ProcessStaticOutput(i, tf_type, data_buf, outputs));
    } else {
      TF_RETURN_IF_ERROR(ProcessDynamicOutput(i, tf_type, data_buf, outputs));
    }
  }
  return Status::OK();
}

void ModelProcess::WorkThread() {
  (void)pthread_setname_np(pthread_self(), "tfa_om_exec");
  ADP_LOG(INFO) << "start work thread " << this;
  bool is_prepared = false;
  while (run_flag_) {
    {
      std::unique_lock<std::mutex> lk{mu_request_};
      ADP_LOG(INFO) << "start wait request.";
      cond_request_.wait(lk, [this] { return (!this->run_flag_.load() || this->request_flag_.load()); });
      request_flag_ = false;
    }
    ADP_LOG(INFO) << "get request, start working.";
    if (!run_flag_) {
      ADP_LOG(INFO) << "stop thread.";
      return;
    }
    if (!is_prepared) {
      thread_ret_ = PrepareProcess();
      if (thread_ret_ == Status::OK()) {
        is_prepared = true;
      } else {
        DestroyResource();
        ADP_LOG(ERROR) << "prepare fail";
        {
          std::unique_lock<std::mutex> lk{mu_reply_};
          reply_flag_ = true;
          cond_reply_.notify_one();
        }
        continue;
      }
    }
    thread_ret_ = Execute(inputs_, outputs_);
    ADP_LOG(INFO) << "execute end " << thread_ret_.ToString();
    {
      std::unique_lock<std::mutex> lk{mu_reply_};
      reply_flag_ = true;
      cond_reply_.notify_one();
    }
  }
}

void ModelProcess::StartWorkThread() {
  run_flag_ = true;
  work_thread_ = std::thread(&ModelProcess::WorkThread, this);
}

Status ModelProcess::MappingTfDtToAcl(const tensorflow::DataType tf_type, aclDataType &acl_type) const {
  const static std::map<tensorflow::DataType, aclDataType> type_mapping = {
      {DT_FLOAT, ACL_FLOAT},  {DT_HALF, ACL_FLOAT16},  {DT_INT8, ACL_INT8},     {DT_INT32, ACL_INT32},
      {DT_UINT8, ACL_UINT8},  {DT_INT16, ACL_INT16},   {DT_UINT16, ACL_UINT16}, {DT_UINT32, ACL_UINT32},
      {DT_INT64, ACL_INT64},  {DT_UINT64, ACL_UINT64}, {DT_DOUBLE, ACL_DOUBLE}, {DT_BOOL, ACL_BOOL},
      {DT_STRING, ACL_STRING}};
  auto found = type_mapping.find(tf_type);
  if (found == type_mapping.end()) {
    return errors::Internal("Unsupported tf data type", DataTypeString(tf_type), " by acl.");
  }
  acl_type = found->second;
  return Status::OK();
}

Status ModelProcess::MappingAclDtToTf(const aclDataType &acl_type, tensorflow::DataType &tf_type) const {
  const static std::map<aclDataType, tensorflow::DataType> type_mapping = {
      {ACL_FLOAT, DT_FLOAT},  {ACL_FLOAT16, DT_HALF},  {ACL_INT8, DT_INT8},     {ACL_INT32, DT_INT32},
      {ACL_UINT8, DT_UINT8},  {ACL_INT16, DT_INT16},   {ACL_UINT16, DT_UINT16}, {ACL_UINT32, DT_UINT32},
      {ACL_INT64, DT_INT64},  {ACL_UINT64, DT_UINT64}, {ACL_DOUBLE, DT_DOUBLE}, {ACL_BOOL, DT_BOOL},
      {ACL_STRING, DT_STRING}};
  auto found = type_mapping.find(acl_type);
  if (found == type_mapping.end()) {
    return errors::Internal("Acl channel receive unsupported data type", acl_type);
  }
  tf_type = found->second;
  return Status::OK();
}

void ModelProcess::UnloadModel() {
  if (!load_flag_) {
    return;
  }
  (void)aclmdlUnload(model_id_);

  if (model_desc_ != nullptr) {
    (void)aclmdlDestroyDesc(model_desc_);
    model_desc_ = nullptr;
  }
  load_flag_ = false;
}

void ModelProcess::DestroyInput() {
  if (input_ == nullptr) {
    return;
  }

  for (size_t i = 0U; i < aclmdlGetDatasetNumBuffers(input_); ++i) {
    aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(input_, i);
    void *data = aclGetDataBufferAddr(dataBuffer);
    (void)aclrtFree(data);
    (void)aclDestroyDataBuffer(dataBuffer);
  }
  (void)aclmdlDestroyDataset(input_);
  input_ = nullptr;
}

void ModelProcess::DestroyOutput() {
  if (output_ == nullptr) {
    return;
  }
  for (size_t i = 0U; i < aclmdlGetDatasetNumBuffers(output_); ++i) {
    aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(output_, i);
    void *data = aclGetDataBufferAddr(dataBuffer);
    (void)aclrtFree(data);
    (void)aclDestroyDataBuffer(dataBuffer);
  }
  (void)aclmdlDestroyDataset(output_);
  output_ = nullptr;
}

void ModelProcess::SendRequest(const std::vector<Tensor> &inputs) {
  inputs_ = inputs;
  ADP_LOG(INFO) << "send request to thread.";
  {
    std::unique_lock<std::mutex> lk{mu_request_};
    request_flag_ = true;
    cond_request_.notify_one();
  }
}

void ModelProcess::WaitReply(std::vector<Tensor> &outputs) {
  {
    std::unique_lock<std::mutex> lk{mu_reply_};
    cond_reply_.wait(lk, [this] { return this->reply_flag_.load(); });
    reply_flag_ = false;
  }
  outputs = outputs_;
}

Status ModelProcess::GetThreadRet() {
  return thread_ret_;
}

Status OmExecutor::Create(const std::string &model_data, std::unique_ptr<OmExecutor> &executor) {
  executor.reset(new (std::nothrow) OmExecutor());
  if (executor == nullptr) {
    return errors::Internal("Failed create executor for om");
  }
  executor->model_process_ = std::unique_ptr<ModelProcess>(new (std::nothrow) ModelProcess(model_data));
  REQUIRES_NOT_NULL(executor->model_process_);
  return Status::OK();
}

Status OmExecutor::Execute(const std::vector<Tensor> &inputs, std::vector<Tensor> &outputs) {
  ADP_LOG(INFO) << "send request start.";
  model_process_->SendRequest(inputs);
  ADP_LOG(INFO) << "send request end, start wait thread reply.";
  model_process_->WaitReply(outputs);
  auto ret = model_process_->GetThreadRet();
  ADP_LOG(INFO) << "execute end " << ret.ToString();
  return ret;
}
}  // namespace tensorflow