TensorFlow Serving C++ API Documentation
tfrt_regressor_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 #include "tensorflow_serving/servables/tensorflow/servable.h"
9 
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.
15 ==============================================================================*/
16 
17 #include <memory>
18 #include <string>
19 #include <utility>
20 #include <vector>
21 
22 #include "google/protobuf/map.h"
23 #include "tensorflow/cc/saved_model/signature_constants.h"
24 #include "tensorflow/core/example/example.pb.h"
25 #include "tensorflow/core/example/feature.pb.h"
26 #include "tensorflow/core/framework/types.pb.h"
27 #include "tensorflow/core/lib/core/errors.h"
28 #include "tensorflow/core/lib/core/status.h"
29 #include "tensorflow/core/lib/core/status_test_util.h"
30 #include "tensorflow/core/platform/mutex.h"
31 #include "tensorflow/core/platform/threadpool_options.h"
32 #include "tensorflow/core/platform/types.h"
33 #include "tensorflow/core/protobuf/error_codes.pb.h"
34 #include "tensorflow/core/public/session.h"
35 #include "tensorflow/core/tfrt/utils/tensor_util.h"
36 #include "tsl/platform/errors.h"
37 #include "tensorflow_serving/apis/input.pb.h"
38 #include "tensorflow_serving/apis/model.pb.h"
39 #include "tensorflow_serving/apis/regression.pb.h"
40 #include "tensorflow_serving/config/model_server_config.pb.h"
41 #include "tensorflow_serving/core/availability_preserving_policy.h"
42 #include "tensorflow_serving/model_servers/model_platform_types.h"
43 #include "tensorflow_serving/model_servers/platform_config_util.h"
44 #include "tensorflow_serving/model_servers/server_core.h"
45 #include "tensorflow_serving/servables/tensorflow/session_bundle_config.pb.h"
46 #include "tensorflow_serving/servables/tensorflow/test_util/mock_tfrt_saved_model.h"
47 #include "tensorflow_serving/servables/tensorflow/tfrt_regressor.h"
48 #include "tensorflow_serving/servables/tensorflow/tfrt_saved_model_source_adapter.pb.h"
49 #include "tensorflow_serving/servables/tensorflow/tfrt_servable.h"
50 #include "tensorflow_serving/test_util/test_util.h"
51 
52 namespace tensorflow {
53 namespace serving {
54 namespace {
55 
56 using ::testing::_;
57 using ::testing::DoAll;
58 using ::testing::HasSubstr;
59 using ::testing::Return;
60 using ::testing::WithArgs;
61 
62 constexpr char kTestModelName[] = "test_model";
63 constexpr int kTestModelVersion = 123;
64 
65 class TfrtRegressorTest : public ::testing::Test {
66  public:
67  static void SetUpTestSuite() {
68  tfrt_stub::SetGlobalRuntime(
69  tfrt_stub::Runtime::Create(/*num_inter_op_threads=*/4));
70  }
71 
72  void SetUp() override {
73  ModelServerConfig config;
74  auto model_config = config.mutable_model_config_list()->add_config();
75  model_config->set_name(kTestModelName);
76  model_config->set_base_path(
77  test_util::TestSrcDirPath("servables/tensorflow/"
78  "testdata/saved_model_half_plus_two_cpu"));
79  model_config->set_model_platform(kTensorFlowModelPlatform);
80 
81  // For ServerCore Options, we leave servable_state_monitor_creator
82  // unspecified so the default servable_state_monitor_creator will be used.
83  ServerCore::Options options;
84  options.model_server_config = config;
85  PlatformConfigMap platform_config_map;
86  ::google::protobuf::Any source_adapter_config;
87  TfrtSavedModelSourceAdapterConfig saved_model_bundle_source_adapter_config;
88  source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
89  (*(*platform_config_map
90  .mutable_platform_configs())[kTensorFlowModelPlatform]
91  .mutable_source_adapter_config()) = source_adapter_config;
92  options.platform_config_map = platform_config_map;
93  options.aspired_version_policy =
94  std::unique_ptr<AspiredVersionPolicy>(new AvailabilityPreservingPolicy);
95  // Reduce the number of initial load threads to be num_load_threads to avoid
96  // timing out in tests.
97  options.num_initial_load_threads = options.num_load_threads;
98  TF_ASSERT_OK(ServerCore::Create(std::move(options), &server_core_));
99 
100  request_ = test_util::CreateProto<RegressionRequest>(
101  "model_spec {"
102  " name: \"test_model\""
103  " signature_name: \"regress_x_to_y\""
104  "}"
105  "input {"
106  " example_list {"
107  " examples {"
108  " features {"
109  " feature: {"
110  " key : \"x\""
111  " value: {"
112  " float_list: {"
113  " value: [ 20.0 ]"
114  " }"
115  " }"
116  " }"
117  " }"
118  " }"
119  " }"
120  "}");
121  }
122 
123  static void TearDownTestSuite() { server_core_ = nullptr; }
124 
125  protected:
126  Status GetSavedModelServableHandle(ServerCore* server_core,
127  ServableHandle<Servable>* servable) {
128  ModelSpec model_spec;
129  model_spec.set_name(kTestModelName);
130  return server_core->GetServableHandle(model_spec, servable);
131  }
132 
133  Status CallRegress(ServerCore* server_core, const RegressionRequest& request,
134  RegressionResponse* response) {
135  ServableHandle<Servable> servable;
136  TF_RETURN_IF_ERROR(GetSavedModelServableHandle(server_core, &servable));
137  tfrt::SavedModel::RunOptions run_options; // Default RunOptions.
138  return RunRegress(
139  run_options, kTestModelVersion,
140  &(down_cast<TfrtSavedModelServable*>(servable.get()))->saved_model(),
141  request, response);
142  }
143 
144  static std::unique_ptr<ServerCore> server_core_;
145 
146  RegressionRequest request_;
147 };
148 
149 std::unique_ptr<ServerCore> TfrtRegressorTest::server_core_;
150 
151 TEST_F(TfrtRegressorTest, Basic) {
152  auto request = test_util::CreateProto<RegressionRequest>(
153  "model_spec {"
154  " name: \"test_model\""
155  " signature_name: \"regress_x_to_y\""
156  "}"
157  "input {"
158  " example_list {"
159  " examples {"
160  " features {"
161  " feature: {"
162  " key : \"x\""
163  " value: {"
164  " float_list: {"
165  " value: [ 80.0 ]"
166  " }"
167  " }"
168  " }"
169  " feature: {"
170  " key : \"locale\""
171  " value: {"
172  " bytes_list: {"
173  " value: [ \"pt_BR\" ]"
174  " }"
175  " }"
176  " }"
177  " feature: {"
178  " key : \"age\""
179  " value: {"
180  " float_list: {"
181  " value: [ 19.0 ]"
182  " }"
183  " }"
184  " }"
185  " }"
186  " }"
187  " examples {"
188  " features {"
189  " feature: {"
190  " key : \"x\""
191  " value: {"
192  " float_list: {"
193  " value: [ 20.0 ]"
194  " }"
195  " }"
196  " }"
197  " }"
198  " }"
199  " }"
200  "}");
201  RegressionResponse response;
202 
203  TF_EXPECT_OK(CallRegress(server_core_.get(), request, &response));
204  EXPECT_THAT(
205  response,
206  test_util::EqualsProto(
207  "result { regressions { value: 42 } regressions { value: 12 }}"
208  "model_spec {"
209  " name: \"test_model\""
210  " signature_name: \"regress_x_to_y\""
211  " version { value: 123 }"
212  "}"));
213 }
214 
215 TEST_F(TfrtRegressorTest, BasicWithContext) {
216  auto request = test_util::CreateProto<RegressionRequest>(
217  "model_spec {"
218  " name: \"test_model\""
219  " signature_name: \"regress_x_to_y\""
220  "}"
221  "input {"
222  " example_list_with_context {"
223  " examples {"
224  " features {"
225  " feature: {"
226  " key : \"x\""
227  " value: {"
228  " float_list: {"
229  " value: [ 80.0 ]"
230  " }"
231  " }"
232  " }"
233  " feature: {"
234  " key : \"locale\""
235  " value: {"
236  " bytes_list: {"
237  " value: [ \"pt_BR\" ]"
238  " }"
239  " }"
240  " }"
241  " feature: {"
242  " key : \"age\""
243  " value: {"
244  " float_list: {"
245  " value: [ 19.0 ]"
246  " }"
247  " }"
248  " }"
249  " }"
250  " }"
251  " examples {"
252  " features {"
253  " feature: {"
254  " key : \"x\""
255  " value: {"
256  " float_list: {"
257  " value: [ 20.0 ]"
258  " }"
259  " }"
260  " }"
261  " }"
262  " }"
263  " context: {"
264  " features: {"
265  " feature: {"
266  " key : \"x\""
267  " value: {"
268  " float_list: {"
269  " value: [ 10.0 ]"
270  " }"
271  " }"
272  " }"
273  " }"
274  " }"
275  " }"
276  "}");
277  RegressionResponse response;
278 
279  TF_EXPECT_OK(CallRegress(server_core_.get(), request, &response));
280  EXPECT_THAT(
281  response,
282  test_util::EqualsProto(
283  "result { regressions { value: 42 } regressions { value: 12 }}"
284  "model_spec {"
285  " name: \"test_model\""
286  " signature_name: \"regress_x_to_y\""
287  " version { value: 123 }"
288  "}"));
289 }
290 
291 TEST_F(TfrtRegressorTest, EmptyExampleList) {
292  auto request = test_util::CreateProto<RegressionRequest>(
293  "model_spec {"
294  " name: \"test_model\""
295  " signature_name: \"regress_x_to_y\""
296  "}"
297  "input {"
298  " example_list {"
299  " }"
300  "}");
301  RegressionResponse response;
302 
303  Status status = CallRegress(server_core_.get(), request, &response);
304  EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
305  EXPECT_THAT(status.message(), ::testing::HasSubstr("Input is empty"));
306 }
307 
308 TEST_F(TfrtRegressorTest, EmptyExampleListWithContext) {
309  auto request = test_util::CreateProto<RegressionRequest>(
310  "model_spec {"
311  " name: \"test_model\""
312  " signature_name: \"regress_x_to_y\""
313  "}"
314  "input {"
315  " example_list_with_context {"
316  " context: {"
317  " features: {"
318  " feature: {"
319  " key : \"x\""
320  " value: {"
321  " float_list: {"
322  " value: [ 10.0 ]"
323  " }"
324  " }"
325  " }"
326  " }"
327  " }"
328  " }"
329  "}");
330  RegressionResponse response;
331 
332  Status status = CallRegress(server_core_.get(), request, &response);
333  EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
334  EXPECT_THAT(status.message(), ::testing::HasSubstr("Input is empty"));
335 }
336 
337 TEST_F(TfrtRegressorTest, EmptyInput) {
338  auto request = test_util::CreateProto<RegressionRequest>(
339  "model_spec {"
340  " name: \"test_model\""
341  " signature_name: \"regress_x_to_y\""
342  "}"
343  "input {"
344  "}");
345  RegressionResponse response;
346 
347  Status status = CallRegress(server_core_.get(), request, &response);
348  EXPECT_EQ(status.code(), error::INVALID_ARGUMENT);
349  EXPECT_THAT(status.message(), ::testing::HasSubstr("Input is empty"));
350 }
351 
352 TEST_F(TfrtRegressorTest, InvalidFunctionName) {
353  RegressionResponse response;
354  std::unique_ptr<test_util::MockSavedModel> saved_model(
355  (new test_util::MockSavedModel()));
356  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
357  .Times(1)
358  .WillRepeatedly(Return(std::nullopt));
359  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
360  saved_model.get(), request_, &response);
361  EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
362  EXPECT_THAT(status.message(), HasSubstr("not found"));
363 }
364 
365 TEST_F(TfrtRegressorTest, InvalidFunctionUnmatchedInputSize) {
366  RegressionResponse response;
367  std::unique_ptr<test_util::MockSavedModel> saved_model(
368  (new test_util::MockSavedModel()));
369  tfrt::internal::Signature signature;
370  signature.input_names = {kRegressInputs, "wrong input"};
371  signature.output_names = {kRegressOutputs};
372  tfrt::FunctionMetadata function_metadata(&signature);
373  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
374  .Times(1)
375  .WillRepeatedly(Return(function_metadata));
376  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
377  saved_model.get(), request_, &response);
378  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
379  EXPECT_THAT(status.message(), HasSubstr("Expected one input Tensor."));
380 }
381 
382 TEST_F(TfrtRegressorTest, InvalidFunctionUnmatchedOutputSize) {
383  RegressionResponse response;
384  std::unique_ptr<test_util::MockSavedModel> saved_model(
385  (new test_util::MockSavedModel()));
386  tfrt::internal::Signature signature;
387  signature.input_names = {kRegressInputs};
388  signature.output_names = {kRegressOutputs, "wrong output"};
389  tfrt::FunctionMetadata function_metadata(&signature);
390  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
391  .Times(1)
392  .WillRepeatedly(Return(function_metadata));
393  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
394  saved_model.get(), request_, &response);
395  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
396  EXPECT_THAT(status.message(), HasSubstr("Expected one output Tensor."));
397 }
398 
399 TEST_F(TfrtRegressorTest, InvalidFunctionInvalidInputName) {
400  RegressionResponse response;
401  std::unique_ptr<test_util::MockSavedModel> saved_model(
402  (new test_util::MockSavedModel()));
403  tfrt::internal::Signature signature;
404  signature.input_names = {"wrong input"};
405  signature.output_names = {kRegressOutputs};
406  tfrt::FunctionMetadata function_metadata(&signature);
407  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
408  .Times(1)
409  .WillRepeatedly(Return(function_metadata));
410  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
411  saved_model.get(), request_, &response);
412  EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
413  EXPECT_THAT(status.message(),
414  HasSubstr("No regression inputs found in function's metadata"));
415 }
416 
417 TEST_F(TfrtRegressorTest, InvalidFunctionInvalidOutputName) {
418  RegressionResponse response;
419  std::unique_ptr<test_util::MockSavedModel> saved_model(
420  (new test_util::MockSavedModel()));
421  tfrt::internal::Signature signature;
422  signature.input_names = {kRegressInputs};
423  signature.output_names = {"wrong output"};
424  tfrt::FunctionMetadata function_metadata(&signature);
425  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
426  .Times(1)
427  .WillRepeatedly(Return(function_metadata));
428  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
429  saved_model.get(), request_, &response);
430  EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
431  EXPECT_THAT(status.message(),
432  HasSubstr("No regression outputs found in function's metadata"));
433 }
434 
435 TEST_F(TfrtRegressorTest, RunsFails) {
436  RegressionResponse response;
437  std::unique_ptr<test_util::MockSavedModel> saved_model(
438  (new test_util::MockSavedModel()));
439  tfrt::internal::Signature signature;
440  signature.input_names = {kRegressInputs};
441  signature.output_names = {kRegressOutputs};
442  tfrt::FunctionMetadata function_metadata(&signature);
443  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
444  .Times(1)
445  .WillRepeatedly(Return(function_metadata));
446  EXPECT_CALL(*saved_model,
447  Run(_, _, ::testing::An<absl::Span<const Tensor>>(), _))
448  .Times(1)
449  .WillRepeatedly(Return(errors::InvalidArgument("test error")));
450  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
451  saved_model.get(), request_, &response);
452  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
453  EXPECT_THAT(status.message(), HasSubstr("test error"));
454 }
455 
456 TEST_F(TfrtRegressorTest, UnexpectedOutputTensorNumber) {
457  RegressionResponse response;
458  std::unique_ptr<test_util::MockSavedModel> saved_model(
459  (new test_util::MockSavedModel()));
460  tfrt::internal::Signature signature;
461  signature.input_names = {kRegressInputs};
462  signature.output_names = {kRegressOutputs};
463  tfrt::FunctionMetadata function_metadata(&signature);
464  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
465  .Times(1)
466  .WillRepeatedly(Return(function_metadata));
467  Tensor output;
468  EXPECT_CALL(*saved_model,
469  Run(_, _, ::testing::An<absl::Span<const Tensor>>(), _))
470  .Times(1)
471  .WillRepeatedly(
472  DoAll(WithArgs<3>([&](std::vector<Tensor>* output_tensors) {
473  output_tensors->push_back(output);
474  output_tensors->push_back(output);
475  }),
476  Return(absl::OkStatus())));
477  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
478  saved_model.get(), request_, &response);
479  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
480  EXPECT_THAT(status.message(),
481  HasSubstr("Expected output_tensors and output_tensor_names to "
482  "have the same size."));
483 }
484 
485 TEST_F(TfrtRegressorTest, UnexpectedOutputTensorShape) {
486  RegressionResponse response;
487  std::unique_ptr<test_util::MockSavedModel> saved_model(
488  (new test_util::MockSavedModel()));
489  tfrt::internal::Signature signature;
490  signature.input_names = {kRegressInputs};
491  signature.output_names = {kRegressOutputs};
492  tfrt::FunctionMetadata function_metadata(&signature);
493  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
494  .Times(1)
495  .WillRepeatedly(Return(function_metadata));
496  Tensor output(DT_FLOAT, TensorShape({1, 1, 1}));
497  EXPECT_CALL(*saved_model,
498  Run(_, _, ::testing::An<absl::Span<const Tensor>>(), _))
499  .Times(1)
500  .WillRepeatedly(
501  DoAll(WithArgs<3>([&](std::vector<Tensor>* output_tensors) {
502  output_tensors->push_back(output);
503  }),
504  Return(absl::OkStatus())));
505  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
506  saved_model.get(), request_, &response);
507  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
508  EXPECT_THAT(status.message(),
509  HasSubstr("Expected output Tensor shape to be either"));
510 }
511 
512 TEST_F(TfrtRegressorTest, UnexpectedOutputTensorSize) {
513  RegressionResponse response;
514  std::unique_ptr<test_util::MockSavedModel> saved_model(
515  (new test_util::MockSavedModel()));
516  tfrt::internal::Signature signature;
517  signature.input_names = {kRegressInputs};
518  signature.output_names = {kRegressOutputs};
519  tfrt::FunctionMetadata function_metadata(&signature);
520  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
521  .Times(1)
522  .WillRepeatedly(Return(function_metadata));
523  Tensor output(DT_FLOAT, TensorShape({3}));
524  EXPECT_CALL(*saved_model,
525  Run(_, _, ::testing::An<absl::Span<const Tensor>>(), _))
526  .Times(1)
527  .WillRepeatedly(
528  DoAll(WithArgs<3>([&](std::vector<Tensor>* output_tensors) {
529  output_tensors->push_back(output);
530  }),
531  Return(absl::OkStatus())));
532  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
533  saved_model.get(), request_, &response);
534  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
535  EXPECT_THAT(status.message(),
536  HasSubstr("Input batch size did not match output batch size"));
537 }
538 
539 TEST_F(TfrtRegressorTest, UnexpectedOutputTensorType) {
540  RegressionResponse response;
541  std::unique_ptr<test_util::MockSavedModel> saved_model(
542  (new test_util::MockSavedModel()));
543  tfrt::internal::Signature signature;
544  signature.input_names = {kRegressInputs};
545  signature.output_names = {kRegressOutputs};
546  tfrt::FunctionMetadata function_metadata(&signature);
547  EXPECT_CALL(*saved_model, GetFunctionMetadata(_))
548  .Times(1)
549  .WillRepeatedly(Return(function_metadata));
550  Tensor output(DT_STRING, TensorShape({1}));
551  EXPECT_CALL(*saved_model,
552  Run(_, _, ::testing::An<absl::Span<const Tensor>>(), _))
553  .Times(1)
554  .WillRepeatedly(
555  DoAll(WithArgs<3>([&](std::vector<Tensor>* output_tensors) {
556  output_tensors->push_back(output);
557  }),
558  Return(absl::OkStatus())));
559  auto status = RunRegress(tfrt::SavedModel::RunOptions(), kTestModelVersion,
560  saved_model.get(), request_, &response);
561  EXPECT_EQ(status.code(), absl::StatusCode::kInvalidArgument);
562  EXPECT_THAT(status.message(),
563  HasSubstr("Expected output Tensor of DT_FLOAT."));
564 }
565 
566 } // namespace
567 } // namespace serving
568 } // namespace tensorflow
static Status Create(Options options, std::unique_ptr< ServerCore > *core)
Definition: server_core.cc:231