TensorFlow Serving C++ API Documentation
multi_inference_test.cc
1 /* Copyright 2017 Google Inc. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7  http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 
16 #include "tensorflow_serving/servables/tensorflow/multi_inference.h"
17 
18 #include <memory>
19 #include <type_traits>
20 #include <utility>
21 #include <vector>
22 
23 #include <gmock/gmock.h>
24 #include <gtest/gtest.h>
25 #include "tensorflow/cc/saved_model/loader.h"
26 #include "tensorflow/cc/saved_model/signature_constants.h"
27 #include "tensorflow/core/example/example.pb.h"
28 #include "tensorflow/core/example/feature.pb.h"
29 #include "tensorflow/core/lib/core/status_test_util.h"
30 #include "tensorflow_serving/apis/classification.pb.h"
31 #include "tensorflow_serving/apis/input.pb.h"
32 #include "tensorflow_serving/apis/regression.pb.h"
33 #include "tensorflow_serving/core/availability_preserving_policy.h"
34 #include "tensorflow_serving/model_servers/model_platform_types.h"
35 #include "tensorflow_serving/model_servers/platform_config_util.h"
36 #include "tensorflow_serving/model_servers/server_core.h"
37 #include "tensorflow_serving/servables/tensorflow/session_bundle_config.pb.h"
38 #include "tensorflow_serving/servables/tensorflow/util.h"
39 #include "tensorflow_serving/test_util/test_util.h"
40 
41 namespace tensorflow {
42 namespace serving {
43 namespace {
44 
45 constexpr char kTestModelName[] = "test_model";
46 
47 // Test fixture for MultiInferenceTest related tests sets up a ServerCore
48 // pointing to TF1 or TF2 version of half_plus_two SavedModel (based on `T`).
49 typedef std::integral_constant<int, 1> tf1_model_t;
50 typedef std::integral_constant<int, 2> tf2_model_t;
51 
52 template <typename T>
53 class MultiInferenceTest : public ::testing::Test {
54  public:
55  static void SetUpTestSuite() {
56  SetSignatureMethodNameCheckFeature(UseTf1Model());
57  TF_ASSERT_OK(CreateServerCore(&server_core_));
58  }
59 
60  static void TearDownTestSuite() { server_core_.reset(); }
61 
62  protected:
63  static Status CreateServerCore(std::unique_ptr<ServerCore>* server_core) {
64  ModelServerConfig config;
65  auto model_config = config.mutable_model_config_list()->add_config();
66  model_config->set_name(kTestModelName);
67  const auto& tf1_saved_model = test_util::TensorflowTestSrcDirPath(
68  "cc/saved_model/testdata/half_plus_two");
69  const auto& tf2_saved_model = test_util::TestSrcDirPath(
70  "/servables/tensorflow/testdata/saved_model_half_plus_two_tf2_cpu");
71  model_config->set_base_path(UseTf1Model() ? tf1_saved_model
72  : tf2_saved_model);
73  model_config->set_model_platform(kTensorFlowModelPlatform);
74 
75  // For ServerCore Options, we leave servable_state_monitor_creator
76  // unspecified so the default servable_state_monitor_creator will be used.
77  ServerCore::Options options;
78  options.model_server_config = config;
79  options.platform_config_map =
80  CreateTensorFlowPlatformConfigMap(SessionBundleConfig());
81  // Reduce the number of initial load threads to be num_load_threads to avoid
82  // timing out in tests.
83  options.num_initial_load_threads = options.num_load_threads;
84  options.aspired_version_policy =
85  std::unique_ptr<AspiredVersionPolicy>(new AvailabilityPreservingPolicy);
86  return ServerCore::Create(std::move(options), server_core);
87  }
88 
89  static bool UseTf1Model() { return std::is_same<T, tf1_model_t>::value; }
90 
91  ServerCore* GetServerCore() { return this->server_core_.get(); }
92 
93  Status GetInferenceRunner(
94  std::unique_ptr<TensorFlowMultiInferenceRunner>* inference_runner) {
95  ServableHandle<SavedModelBundle> bundle;
96  ModelSpec model_spec;
97  model_spec.set_name(kTestModelName);
98  TF_RETURN_IF_ERROR(GetServerCore()->GetServableHandle(model_spec, &bundle));
99 
100  inference_runner->reset(new TensorFlowMultiInferenceRunner(
101  bundle->session.get(), &bundle->meta_graph_def,
102  {this->servable_version_}));
103  return absl::OkStatus();
104  }
105 
106  Status GetServableHandle(ServableHandle<SavedModelBundle>* bundle) {
107  ModelSpec model_spec;
108  model_spec.set_name(kTestModelName);
109  return GetServerCore()->GetServableHandle(model_spec, bundle);
110  }
111 
112  const int64_t servable_version_ = 1;
113 
114  private:
115  static std::unique_ptr<ServerCore> server_core_;
116 };
117 
118 template <typename T>
119 std::unique_ptr<ServerCore> MultiInferenceTest<T>::server_core_;
120 
121 TYPED_TEST_SUITE_P(MultiInferenceTest);
122 
124 // Test Helpers
125 
126 void AddInput(const std::vector<std::pair<string, float>>& feature_kv,
127  MultiInferenceRequest* request) {
128  auto* example =
129  request->mutable_input()->mutable_example_list()->add_examples();
130  auto* features = example->mutable_features()->mutable_feature();
131  for (const auto& feature : feature_kv) {
132  (*features)[feature.first].mutable_float_list()->add_value(feature.second);
133  }
134 }
135 
136 void PopulateTask(const string& signature_name, const string& method_name,
137  InferenceTask* task) {
138  ModelSpec model_spec;
139  model_spec.set_name(kTestModelName);
140  model_spec.set_signature_name(signature_name);
141  *task->mutable_model_spec() = model_spec;
142  task->set_method_name(method_name);
143 }
144 
145 void ExpectStatusError(const Status& status,
146  const tensorflow::errors::Code expected_code,
147  const string& message_substring) {
148  EXPECT_EQ(expected_code, status.code());
149  EXPECT_THAT(status.message(), ::testing::HasSubstr(message_substring));
150 }
151 
153 // Tests
154 
155 TYPED_TEST_P(MultiInferenceTest, MissingInputTest) {
156  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
157  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
158 
159  MultiInferenceRequest request;
160  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
161 
162  MultiInferenceResponse response;
163  ExpectStatusError(
164  inference_runner->Infer(RunOptions(), request, &response),
165  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
166  "Input is empty");
167 
168  // MultiInference testing
169  ServableHandle<SavedModelBundle> bundle;
170  TF_ASSERT_OK(this->GetServableHandle(&bundle));
171  ExpectStatusError(
172  RunMultiInference(RunOptions(), bundle->meta_graph_def,
173  this->servable_version_, bundle->session.get(), request,
174  &response),
175  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
176  "Input is empty");
177 }
178 
179 TYPED_TEST_P(MultiInferenceTest, UndefinedSignatureTest) {
180  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
181  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
182 
183  MultiInferenceRequest request;
184  AddInput({{"x", 2}}, &request);
185  PopulateTask("ThisSignatureDoesNotExist", kRegressMethodName,
186  request.add_tasks());
187 
188  MultiInferenceResponse response;
189  ExpectStatusError(
190  inference_runner->Infer(RunOptions(), request, &response),
191  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
192  "signature not found");
193 
194  // MultiInference testing
195  ServableHandle<SavedModelBundle> bundle;
196  TF_ASSERT_OK(this->GetServableHandle(&bundle));
197  ExpectStatusError(
198  RunMultiInference(RunOptions(), bundle->meta_graph_def,
199  this->servable_version_, bundle->session.get(), request,
200  &response),
201  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
202  "signature not found");
203 }
204 
205 // Two ModelSpecs, accessing different models.
206 TYPED_TEST_P(MultiInferenceTest, InconsistentModelSpecsInRequestTest) {
207  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
208  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
209 
210  MultiInferenceRequest request;
211  AddInput({{"x", 2}}, &request);
212  // Valid signature.
213  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
214 
215  // Add invalid Task to request.
216  ModelSpec model_spec;
217  model_spec.set_name("ModelDoesNotExist");
218  model_spec.set_signature_name("regress_x_to_y");
219  auto* task = request.add_tasks();
220  *task->mutable_model_spec() = model_spec;
221  task->set_method_name(kRegressMethodName);
222 
223  MultiInferenceResponse response;
224  ExpectStatusError(
225  inference_runner->Infer(RunOptions(), request, &response),
226  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
227  "must access the same model name");
228 
229  // MultiInference testing
230  ServableHandle<SavedModelBundle> bundle;
231  TF_ASSERT_OK(this->GetServableHandle(&bundle));
232  ExpectStatusError(
233  RunMultiInference(RunOptions(), bundle->meta_graph_def,
234  this->servable_version_, bundle->session.get(), request,
235  &response),
236  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
237  "must access the same model name");
238 }
239 
240 TYPED_TEST_P(MultiInferenceTest, EvaluateDuplicateSignaturesTest) {
241  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
242  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
243 
244  MultiInferenceRequest request;
245  AddInput({{"x", 2}}, &request);
246  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
247  // Add the same task again (error).
248  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
249 
250  MultiInferenceResponse response;
251  ExpectStatusError(
252  inference_runner->Infer(RunOptions(), request, &response),
253  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
254  "Duplicate evaluation of signature: regress_x_to_y");
255 
256  // MultiInference testing
257  ServableHandle<SavedModelBundle> bundle;
258  TF_ASSERT_OK(this->GetServableHandle(&bundle));
259  ExpectStatusError(
260  RunMultiInference(RunOptions(), bundle->meta_graph_def,
261  this->servable_version_, bundle->session.get(), request,
262  &response),
263  static_cast<absl::StatusCode>(absl::StatusCode::kInvalidArgument),
264  "Duplicate evaluation of signature: regress_x_to_y");
265 }
266 
267 TYPED_TEST_P(MultiInferenceTest, UsupportedSignatureTypeTest) {
268  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
269  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
270 
271  MultiInferenceRequest request;
272  AddInput({{"x", 2}}, &request);
273  PopulateTask("serving_default", kPredictMethodName, request.add_tasks());
274 
275  MultiInferenceResponse response;
276  ExpectStatusError(
277  inference_runner->Infer(RunOptions(), request, &response),
278  static_cast<absl::StatusCode>(absl::StatusCode ::kUnimplemented),
279  "Unsupported signature");
280 
281  // MultiInference testing
282  ServableHandle<SavedModelBundle> bundle;
283  TF_ASSERT_OK(this->GetServableHandle(&bundle));
284  ExpectStatusError(
285  RunMultiInference(RunOptions(), bundle->meta_graph_def,
286  this->servable_version_, bundle->session.get(), request,
287  &response),
288  static_cast<absl::StatusCode>(absl::StatusCode::kUnimplemented),
289  "Unsupported signature");
290 }
291 
292 TYPED_TEST_P(MultiInferenceTest, ValidSingleSignatureTest) {
293  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
294  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
295 
296  MultiInferenceRequest request;
297  AddInput({{"x", 2}}, &request);
298  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
299 
300  MultiInferenceResponse expected_response;
301  auto* inference_result = expected_response.add_results();
302  auto* model_spec = inference_result->mutable_model_spec();
303  *model_spec = request.tasks(0).model_spec();
304  model_spec->mutable_version()->set_value(this->servable_version_);
305  auto* regression_result = inference_result->mutable_regression_result();
306  regression_result->add_regressions()->set_value(3.0);
307 
308  MultiInferenceResponse response;
309  TF_ASSERT_OK(inference_runner->Infer(RunOptions(), request, &response));
310  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
311 
312  // MultiInference testing
313  response.Clear();
314  ServableHandle<SavedModelBundle> bundle;
315  TF_ASSERT_OK(this->GetServableHandle(&bundle));
316  TF_ASSERT_OK(RunMultiInference(RunOptions(), bundle->meta_graph_def,
317  this->servable_version_, bundle->session.get(),
318  request, &response));
319  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
320 }
321 
322 TYPED_TEST_P(MultiInferenceTest, MultipleValidRegressSignaturesTest) {
323  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
324  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
325 
326  MultiInferenceRequest request;
327  AddInput({{"x", 2}}, &request);
328  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
329  PopulateTask("regress_x_to_y2", kRegressMethodName, request.add_tasks());
330 
331  MultiInferenceResponse expected_response;
332 
333  // regress_x_to_y is y = 0.5x + 2.
334  auto* inference_result_1 = expected_response.add_results();
335  auto* model_spec_1 = inference_result_1->mutable_model_spec();
336  *model_spec_1 = request.tasks(0).model_spec();
337  model_spec_1->mutable_version()->set_value(this->servable_version_);
338  auto* regression_result_1 = inference_result_1->mutable_regression_result();
339  regression_result_1->add_regressions()->set_value(3.0);
340 
341  // regress_x_to_y2 is y2 = 0.5x + 3.
342  auto* inference_result_2 = expected_response.add_results();
343  auto* model_spec_2 = inference_result_2->mutable_model_spec();
344  *model_spec_2 = request.tasks(1).model_spec();
345  model_spec_2->mutable_version()->set_value(this->servable_version_);
346  auto* regression_result_2 = inference_result_2->mutable_regression_result();
347  regression_result_2->add_regressions()->set_value(4.0);
348 
349  MultiInferenceResponse response;
350  TF_ASSERT_OK(inference_runner->Infer(RunOptions(), request, &response));
351  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
352 
353  // MultiInference testing
354  response.Clear();
355  ServableHandle<SavedModelBundle> bundle;
356  TF_ASSERT_OK(this->GetServableHandle(&bundle));
357  TF_ASSERT_OK(RunMultiInference(RunOptions(), bundle->meta_graph_def,
358  this->servable_version_, bundle->session.get(),
359  request, &response));
360  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
361 }
362 
363 TYPED_TEST_P(MultiInferenceTest, RegressAndClassifySignaturesTest) {
364  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
365  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
366 
367  MultiInferenceRequest request;
368  AddInput({{"x", 2}}, &request);
369  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
370  PopulateTask("classify_x_to_y", kClassifyMethodName, request.add_tasks());
371 
372  MultiInferenceResponse expected_response;
373  auto* inference_result_1 = expected_response.add_results();
374  auto* model_spec_1 = inference_result_1->mutable_model_spec();
375  *model_spec_1 = request.tasks(0).model_spec();
376  model_spec_1->mutable_version()->set_value(this->servable_version_);
377  auto* regression_result = inference_result_1->mutable_regression_result();
378  regression_result->add_regressions()->set_value(3.0);
379 
380  auto* inference_result_2 = expected_response.add_results();
381  auto* model_spec_2 = inference_result_2->mutable_model_spec();
382  *model_spec_2 = request.tasks(1).model_spec();
383  model_spec_2->mutable_version()->set_value(this->servable_version_);
384  auto* classification_result =
385  inference_result_2->mutable_classification_result();
386  classification_result->add_classifications()->add_classes()->set_score(3.0);
387 
388  MultiInferenceResponse response;
389  TF_ASSERT_OK(inference_runner->Infer(RunOptions(), request, &response));
390  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
391 
392  // MultiInference testing
393  response.Clear();
394  ServableHandle<SavedModelBundle> bundle;
395  TF_ASSERT_OK(this->GetServableHandle(&bundle));
396  TF_ASSERT_OK(RunMultiInference(RunOptions(), bundle->meta_graph_def,
397  this->servable_version_, bundle->session.get(),
398  request, &response));
399  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
400 }
401 
402 TYPED_TEST_P(MultiInferenceTest, ThreadPoolOptions) {
403  std::unique_ptr<TensorFlowMultiInferenceRunner> inference_runner;
404  TF_ASSERT_OK(this->GetInferenceRunner(&inference_runner));
405 
406  MultiInferenceRequest request;
407  AddInput({{"x", 2}}, &request);
408  PopulateTask("regress_x_to_y", kRegressMethodName, request.add_tasks());
409 
410  MultiInferenceResponse expected_response;
411  auto* inference_result = expected_response.add_results();
412  auto* model_spec = inference_result->mutable_model_spec();
413  *model_spec = request.tasks(0).model_spec();
414  model_spec->mutable_version()->set_value(this->servable_version_);
415  auto* regression_result = inference_result->mutable_regression_result();
416  regression_result->add_regressions()->set_value(3.0);
417 
418  test_util::CountingThreadPool inter_op_threadpool(Env::Default(), "InterOp",
419  /*num_threads=*/1);
420  test_util::CountingThreadPool intra_op_threadpool(Env::Default(), "IntraOp",
421  /*num_threads=*/1);
422  thread::ThreadPoolOptions thread_pool_options;
423  thread_pool_options.inter_op_threadpool = &inter_op_threadpool;
424  thread_pool_options.intra_op_threadpool = &intra_op_threadpool;
425  MultiInferenceResponse response;
426  ServableHandle<SavedModelBundle> bundle;
427  TF_ASSERT_OK(this->GetServableHandle(&bundle));
428  TF_ASSERT_OK(RunMultiInference(RunOptions(), bundle->meta_graph_def,
429  this->servable_version_, bundle->session.get(),
430  request, &response, thread_pool_options));
431  EXPECT_THAT(response, test_util::EqualsProto(expected_response));
432 
433  // The intra_op_threadpool doesn't have anything scheduled.
434  ASSERT_GE(inter_op_threadpool.NumScheduled(), 1);
435 }
436 
437 REGISTER_TYPED_TEST_SUITE_P(
438  MultiInferenceTest, MissingInputTest, UndefinedSignatureTest,
439  InconsistentModelSpecsInRequestTest, EvaluateDuplicateSignaturesTest,
440  UsupportedSignatureTypeTest, ValidSingleSignatureTest,
441  MultipleValidRegressSignaturesTest, RegressAndClassifySignaturesTest,
442  ThreadPoolOptions);
443 
444 typedef ::testing::Types<tf1_model_t, tf2_model_t> ModelTypes;
445 INSTANTIATE_TYPED_TEST_SUITE_P(MultiInference, MultiInferenceTest, ModelTypes);
446 
447 } // namespace
448 } // namespace serving
449 } // namespace tensorflow
static Status Create(Options options, std::unique_ptr< ServerCore > *core)
Definition: server_core.cc:231