TensorFlow Serving C++ API Documentation
simple_loader.h
1 /* Copyright 2016 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 #ifndef TENSORFLOW_SERVING_CORE_SIMPLE_LOADER_H_
17 #define TENSORFLOW_SERVING_CORE_SIMPLE_LOADER_H_
18 
19 #include <functional>
20 #include <memory>
21 
22 #include "absl/types/optional.h"
23 #include "absl/types/variant.h"
24 #include "tensorflow/core/lib/core/errors.h"
25 #include "tensorflow/core/lib/core/status.h"
26 #include "tensorflow/core/platform/macros.h"
27 #include "tensorflow/core/platform/mem.h"
28 #include "tensorflow/core/platform/mutex.h"
29 #include "tensorflow/core/platform/types.h"
30 #include "tensorflow_serving/core/loader.h"
31 #include "tensorflow_serving/core/source_adapter.h"
32 #include "tensorflow_serving/resources/resource_util.h"
33 #include "tensorflow_serving/resources/resource_values.h"
34 #include "tensorflow_serving/util/any_ptr.h"
35 
36 namespace tensorflow {
37 namespace serving {
38 
39 // SimpleLoader is a wrapper that simplifies Loader creation for common, simple
40 // use-cases that conform to the following restrictions:
41 // - The servable's estimated resource footprint is static.
42 // - The servable can be loaded by invoking a no-argument closure.
43 // - The servable can be unloaded by invoking its destructor.
44 //
45 // When constructing a SimpleLoader users provide a Creator callback. This
46 // callback is used in the Load() method to construct a servable of type
47 // ServableType and populate the servable. The servable object is destroyed when
48 // Unload() is called.
49 //
50 // SimpleLoader uses a second supplied callback to estimate the servable's
51 // resource usage. It memoizes that callback's result, for efficiency. If main-
52 // memory resources are specified, Unload() releases that amount of memory to
53 // the OS after deleting the servable.
54 //
55 // Example use: create a toy Loader for a servable of type time_t. Here the
56 // time servable is instantiated with the current time when Load() is called.
57 // auto servable_creator = [](std::unique_ptr<time_t>* servable) {
58 // servable->reset(new time_t);
59 // *servable = time(nullptr);
60 // return Status();
61 // };
62 // auto resource_estimator = [](ResourceAllocation* estimate) {
63 // estimate->mutable_...(...)->set_...(...);
64 // return Status();
65 // };
66 // std::unique_ptr<Loader> loader(new SimpleLoader<time_t>(
67 // servable_creator, resource_estimator));
68 //
69 // This class is not thread-safe. Synchronization is assumed to be done by the
70 // caller.
71 template <typename ServableType>
72 class SimpleLoader : public Loader {
73  public:
74  // Creator is called in Load and used to create the servable.
75  using Creator = std::function<Status(std::unique_ptr<ServableType>*)>;
76  using CreatorWithMetadata =
77  std::function<Status(const Metadata&, std::unique_ptr<ServableType>*)>;
78  using CreatorVariant = absl::variant<Creator, CreatorWithMetadata>;
79 
80  // A callback for estimating a servable's resource usage.
81  using ResourceEstimator = std::function<Status(ResourceAllocation*)>;
82 
83  // Returns a dummy resource-estimation callback that estimates the servable's
84  // resource footprint at zero. Useful in best-effort or test environments that
85  // do not track resource usage.
86  //
87  // IMPORTANT: Use of EstimateNoResources() abdicates resource safety, i.e. a
88  // loader using that option does not declare its servable's resource usage,
89  // and hence the serving system cannot enforce resource safety.
90  static ResourceEstimator EstimateNoResources();
91 
92  // Constructor that takes a single resource estimator, to use for estimating
93  // the resources needed during load as well as post-load.
94  SimpleLoader(Creator creator, ResourceEstimator resource_estimator);
95 
96  // Similar to the above constructor, but accepts a CreatorWithMetadata
97  // function.
98  SimpleLoader(CreatorWithMetadata creator_with_metadata,
99  ResourceEstimator resource_estimator);
100 
101  // Constructor that takes two resource estimators: one to use for estimating
102  // the resources needed during load, as well as a second one that gives a
103  // different estimate after loading has finished. See the documentation on
104  // Loader::EstimateResources() for (a) potential reasons the estimate might
105  // decrease, and (b) correctness constraints on how the estimate is allowed to
106  // change over time.
107  SimpleLoader(Creator creator, ResourceEstimator resource_estimator,
108  ResourceEstimator post_load_resource_estimator);
109 
110  // Similar to the above constructor, but accepts a CreatorWithMetadata
111  // function.
112  SimpleLoader(CreatorWithMetadata creator_with_metadata,
113  ResourceEstimator resource_estimator,
114  ResourceEstimator post_load_resource_estimator);
115 
116  // Constructor which accepts all variations of the params.
117  SimpleLoader(CreatorVariant creator_variant,
118  ResourceEstimator resource_estimator,
119  absl::optional<ResourceEstimator> post_load_resource_estimator);
120 
121  ~SimpleLoader() override = default;
122 
123  Status EstimateResources(ResourceAllocation* estimate) const override;
124 
125  // REQUIRES: That the ctor with Creator be used, otherwise returns an error
126  // status.
127  Status Load() override;
128 
129  Status LoadWithMetadata(const Metadata& metadata) override;
130 
131  void Unload() override;
132 
133  AnyPtr servable() override { return AnyPtr{servable_.get()}; }
134 
135  private:
136  Status EstimateResourcesPostLoad();
137 
138  CreatorVariant creator_variant_;
139 
140  // A function that estimates the resources needed to load the servable.
141  ResourceEstimator resource_estimator_;
142 
143  // An optional function that estimates the resources needed for the servable
144  // after it has been loaded. (If omitted, 'resource_estimator_' should be used
145  // for all estimates, i.e. before, during and after load.)
146  absl::optional<ResourceEstimator> post_load_resource_estimator_;
147 
148  // The memoized estimated resource requirement of the servable.
149  mutable absl::optional<ResourceAllocation> memoized_resource_estimate_
150  TF_GUARDED_BY(memoized_resource_estimate_mu_);
151  mutable mutex memoized_resource_estimate_mu_;
152 
153  std::unique_ptr<ResourceUtil> resource_util_;
154  Resource ram_resource_;
155 
156  std::unique_ptr<ServableType> servable_;
157 
158  TF_DISALLOW_COPY_AND_ASSIGN(SimpleLoader);
159 };
160 
161 // SimpleLoaderSourceAdapter is used to create a simple SourceAdapter that
162 // creates Loaders for servables of type ServableType (e.g. std::map), from
163 // objects of type DataType (e.g. storage paths of serialized hashmaps).
164 //
165 // It bundles together the SourceAdapter and Loader concepts, in a way that
166 // suffices for simple use cases. In particular, its limitations are:
167 //
168 // - Like UnarySourceAdapter (see source_adapter.h), it translates aspired-
169 // version items one at a time, giving a simpler interface but less flexibility.
170 //
171 // - Like SimpleLoader, the servable's estimated resource footprint is static,
172 // and the emitted loaders' Unload() implementation calls ServableType's
173 // destructor and releases the memory to the OS.
174 //
175 // For more complex behaviors, SimpleLoaderSourceAdapter is inapplicable. You
176 // must instead create a SourceAdapter and Loader. That said, you may still be
177 // able to use one of UnarySourceAdapter or SimpleLoader.
178 //
179 // IMPORTANT: Every leaf derived class must call Detach() at the top of its
180 // destructor. (See documentation on TargetBase::Detach() in target.h.) Doing so
181 // ensures that no virtual method calls are in flight during destruction of
182 // member variables.
183 template <typename DataType, typename ServableType>
185  : public UnarySourceAdapter<DataType, std::unique_ptr<Loader>> {
186  public:
187  ~SimpleLoaderSourceAdapter() override = 0;
188 
189  // Creator is called by the produced Loaders' Load() method, and used to
190  // create objects of type ServableType. It takes a DataType object as input.
191  using Creator =
192  std::function<Status(const DataType&, std::unique_ptr<ServableType>*)>;
193 
194  // A callback for estimating a servable's resource usage. It takes a DataType
195  // object as input.
196  using ResourceEstimator =
197  std::function<Status(const DataType&, ResourceAllocation*)>;
198 
199  // Returns a dummy resource-estimation callback that estimates the servable's
200  // resource footprint at zero. Useful in best-effort or test environments that
201  // do not track resource usage.
202  //
203  // IMPORTANT: Use of EstimateNoResources() abdicates resource safety, i.e. a
204  // loader using that option does not declare its servable's resource usage,
205  // and hence the serving system cannot enforce resource safety.
206  static ResourceEstimator EstimateNoResources();
207 
208  protected:
209  // This is an abstract class.
210  SimpleLoaderSourceAdapter(Creator creator,
211  ResourceEstimator resource_estimator);
212 
213  Status Convert(const DataType& data, std::unique_ptr<Loader>* loader) final;
214 
215  private:
216  Creator creator_;
217  ResourceEstimator resource_estimator_;
218 
219  TF_DISALLOW_COPY_AND_ASSIGN(SimpleLoaderSourceAdapter);
220 };
221 
223 // Implementation details follow. API users need not read.
224 
225 template <typename ServableType>
226 typename SimpleLoader<ServableType>::ResourceEstimator
228  return [](ResourceAllocation* estimate) {
229  estimate->Clear();
230  return Status();
231  };
232 }
233 
234 template <typename ServableType>
235 SimpleLoader<ServableType>::SimpleLoader(Creator creator,
236  ResourceEstimator resource_estimator)
237  : SimpleLoader(CreatorVariant(creator), resource_estimator, absl::nullopt) {
238 }
239 
240 template <typename ServableType>
241 SimpleLoader<ServableType>::SimpleLoader(
242  CreatorWithMetadata creator_with_metadata,
243  ResourceEstimator resource_estimator)
244  : SimpleLoader(CreatorVariant(creator_with_metadata), resource_estimator,
245  absl::nullopt) {}
246 
247 template <typename ServableType>
248 SimpleLoader<ServableType>::SimpleLoader(
249  Creator creator, ResourceEstimator resource_estimator,
250  ResourceEstimator post_load_resource_estimator)
251  : SimpleLoader(CreatorVariant(creator), resource_estimator,
252  {post_load_resource_estimator}) {}
253 
254 template <typename ServableType>
255 SimpleLoader<ServableType>::SimpleLoader(
256  CreatorWithMetadata creator_with_metadata,
257  ResourceEstimator resource_estimator,
258  ResourceEstimator post_load_resource_estimator)
259  : SimpleLoader(CreatorVariant(creator_with_metadata), resource_estimator,
260  {post_load_resource_estimator}) {}
261 
262 template <typename ServableType>
263 SimpleLoader<ServableType>::SimpleLoader(
264  CreatorVariant creator_variant, ResourceEstimator resource_estimator,
265  absl::optional<ResourceEstimator> post_load_resource_estimator)
266  : creator_variant_(creator_variant),
267  resource_estimator_(resource_estimator),
268  post_load_resource_estimator_(post_load_resource_estimator) {
269  ResourceUtil::Options resource_util_options;
270  resource_util_options.devices = {{device_types::kMain, 1}};
271  resource_util_ =
272  std::unique_ptr<ResourceUtil>(new ResourceUtil(resource_util_options));
273 
274  ram_resource_ = resource_util_->CreateBoundResource(
275  device_types::kMain, resource_kinds::kRamBytes);
276 }
277 
278 template <typename ServableType>
280  ResourceAllocation* estimate) const {
281  mutex_lock l(memoized_resource_estimate_mu_);
282  if (memoized_resource_estimate_) {
283  *estimate = *memoized_resource_estimate_;
284  return Status();
285  }
286 
287  // Compute and memoize the resource estimate.
288  TF_RETURN_IF_ERROR(resource_estimator_(estimate));
289  memoized_resource_estimate_ = *estimate;
290  return Status();
291 }
292 
293 template <typename ServableType>
295  if (absl::holds_alternative<CreatorWithMetadata>(creator_variant_)) {
296  return errors::FailedPrecondition(
297  "SimpleLoader::Load() called even though "
298  "SimpleLoader::CreatorWithMetadata was setup. Please use "
299  "SimpleLoader::LoadWithMetadata() instead.");
300  }
301  TF_RETURN_IF_ERROR(absl::get<Creator>(creator_variant_)(&servable_));
302  return EstimateResourcesPostLoad();
303 }
304 
305 template <typename ServableType>
307  if (absl::holds_alternative<CreatorWithMetadata>(creator_variant_)) {
308  TF_RETURN_IF_ERROR(
309  absl::get<CreatorWithMetadata>(creator_variant_)(metadata, &servable_));
310  } else {
311  TF_RETURN_IF_ERROR(absl::get<Creator>(creator_variant_)(&servable_));
312  }
313  return EstimateResourcesPostLoad();
314 }
315 
316 template <typename ServableType>
318  if (post_load_resource_estimator_) {
319  // Save the during-load estimate (may be able to use the memoized value).
320  ResourceAllocation during_load_resource_estimate;
321  TF_RETURN_IF_ERROR(EstimateResources(&during_load_resource_estimate));
322 
323  // Obtain the post-load estimate, and store it as the memoized value.
324  ResourceAllocation post_load_resource_estimate;
325  TF_RETURN_IF_ERROR(
326  (*post_load_resource_estimator_)(&post_load_resource_estimate));
327  {
328  mutex_lock l(memoized_resource_estimate_mu_);
329  memoized_resource_estimate_ = post_load_resource_estimate;
330  }
331 
332  // Release any transient memory used only during load to the OS.
333  const uint64_t during_load_ram_estimate = resource_util_->GetQuantity(
334  ram_resource_, during_load_resource_estimate);
335  const uint64_t post_load_ram_estimate =
336  resource_util_->GetQuantity(ram_resource_, post_load_resource_estimate);
337  if (post_load_ram_estimate < during_load_ram_estimate) {
338  const uint64_t transient_ram_estimate =
339  during_load_ram_estimate - post_load_ram_estimate;
340  LOG(INFO) << "Calling MallocExtension_ReleaseToSystem() after servable "
341  "load with "
342  << transient_ram_estimate;
343  ::tensorflow::port::MallocExtension_ReleaseToSystem(
344  transient_ram_estimate);
345  }
346  }
347 
348  return Status();
349 }
350 
351 template <typename ServableType>
353  // Before destroying the servable, run the resource estimator (in case the
354  // estimation routine calls into the servable behind the scenes.)
355  ResourceAllocation resource_estimate;
356  Status resource_status = EstimateResources(&resource_estimate);
357 
358  // Delete the servable no matter what (even if the resource estimator had some
359  // error).
360  servable_.reset();
361 
362  if (!resource_status.ok()) {
363  return;
364  }
365 
366  // If we have a main-memory footprint estimate, release that amount of memory
367  // to the OS.
368  const uint64_t memory_estimate =
369  resource_util_->GetQuantity(ram_resource_, resource_estimate);
370  if (memory_estimate > 0) {
371  LOG(INFO) << "Calling MallocExtension_ReleaseToSystem() after servable "
372  "unload with "
373  << memory_estimate;
374  ::tensorflow::port::MallocExtension_ReleaseToSystem(memory_estimate);
375  }
376 }
377 
378 template <typename DataType, typename ServableType>
380  ServableType>::~SimpleLoaderSourceAdapter() {}
381 
382 template <typename DataType, typename ServableType>
383 typename SimpleLoaderSourceAdapter<DataType, ServableType>::ResourceEstimator
384 SimpleLoaderSourceAdapter<DataType, ServableType>::EstimateNoResources() {
385  return [](const DataType& data, ResourceAllocation* estimate) {
386  estimate->Clear();
387  return Status();
388  };
389 }
390 
391 template <typename DataType, typename ServableType>
392 SimpleLoaderSourceAdapter<DataType, ServableType>::SimpleLoaderSourceAdapter(
393  Creator creator, ResourceEstimator resource_estimator)
394  : creator_(creator), resource_estimator_(resource_estimator) {}
395 
396 template <typename DataType, typename ServableType>
397 Status SimpleLoaderSourceAdapter<DataType, ServableType>::Convert(
398  const DataType& data, std::unique_ptr<Loader>* loader) {
399  // We copy 'creator_' and 'resource_estimator_', rather than passing via
400  // reference, so that the loader we emit is not tied to the adapter, in case
401  // the adapter is deleted before the loader.
402  const auto creator = creator_;
403  const auto resource_estimator = resource_estimator_;
404  loader->reset(new SimpleLoader<ServableType>(
405  [creator, data](std::unique_ptr<ServableType>* servable) {
406  return creator(data, servable);
407  },
408  [resource_estimator, data](ResourceAllocation* estimate) {
409  return resource_estimator(data, estimate);
410  }));
411  return Status();
412 }
413 
414 } // namespace serving
415 } // namespace tensorflow
416 
417 #endif // TENSORFLOW_SERVING_CORE_SIMPLE_LOADER_H_
Status EstimateResources(ResourceAllocation *estimate) const override
Status LoadWithMetadata(const Metadata &metadata) override
The metadata consists of the ServableId.
Definition: loader.h:94