Beispiel #1
0
 def test_signature_attribute_reserved(self):
   root = util.Checkpoint(signatures=variables.Variable(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(ValueError, "del obj.signatures"):
     save.save(root, save_dir)
   del root.signatures
   save.save(root, save_dir)
  def test_asset_loading(self):
    first_path = self._v1_asset_saved_model()
    imported = load.load(first_path)
    self.evaluate(lookup_ops.tables_initializer())
    fn = imported.signatures["serving_default"]
    self.assertAllClose({"output": [2, 0]},
                        fn(start=constant_op.constant(["gamma", "alpha"])))
    second_path = os.path.join(self.get_temp_dir(), "saved_model",
                               str(ops.uid()))
    save.save(imported, second_path, signatures=imported.signatures)
    shutil.rmtree(first_path)
    del ops.get_collection_ref(ops.GraphKeys.TABLE_INITIALIZERS)[:]
    second_import = load.load(second_path)
    self.evaluate(lookup_ops.tables_initializer())
    fn = second_import.signatures["serving_default"]
    self.assertAllClose({"output": [2, 0]},
                        fn(start=constant_op.constant(["gamma", "alpha"])))

    third_path = os.path.join(self.get_temp_dir(), "saved_model",
                              str(ops.uid()))
    save.save(second_import, third_path, signatures=second_import.signatures)
    shutil.rmtree(second_path)
    del ops.get_collection_ref(ops.GraphKeys.TABLE_INITIALIZERS)[:]
    third_import = load.load(third_path)
    self.evaluate(lookup_ops.tables_initializer())
    fn = third_import.signatures["serving_default"]
    self.assertAllClose({"output": [2, 0]},
                        fn(start=constant_op.constant(["gamma", "alpha"])))
  def testConstSavedModel(self):
    """Test a basic model with functions to make sure functions are inlined."""
    input_data = constant_op.constant(1., shape=[1])
    root = tracking.AutoTrackable()
    root.f = def_function.function(lambda x: 2. * x)
    to_save = root.f.get_concrete_function(input_data)

    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    save(root, save_dir, to_save)
    saved_model = load(save_dir)
    input_func = saved_model.signatures["serving_default"]

    variable_graph_def = input_func.graph.as_graph_def()
    self.assertEqual(0, self._getNumVariables(variable_graph_def))
    self.assertTrue(variable_graph_def.library.function)

    output_func = convert_to_constants.convert_variables_to_constants_v2(
        input_func)
    constant_graph_def = output_func.graph.as_graph_def()
    self.assertEqual(0, self._getNumVariables(constant_graph_def))
    self.assertFalse(constant_graph_def.library.function)

    # Check value.
    expected_value = root.f(input_data)
    actual_value = self._evaluateGraphDef(constant_graph_def, input_func,
                                          [input_data.numpy()])
    self.assertEqual(expected_value.numpy(), actual_value)
Beispiel #4
0
 def test_table(self):
   initializer = lookup_ops.TextFileInitializer(
       self._vocab_path,
       key_dtype=dtypes.string,
       key_index=lookup_ops.TextFileIndex.WHOLE_LINE,
       value_dtype=dtypes.int64,
       value_index=lookup_ops.TextFileIndex.LINE_NUMBER)
   root = util.Checkpoint(table=lookup_ops.HashTable(
       initializer, default_value=-1))
   root.table_user = def_function.function(
       root.table.lookup,
       input_signature=[tensor_spec.TensorSpec(None, dtypes.string)])
   self.assertEqual(
       2,
       self.evaluate(root.table_user(constant_op.constant("gamma"))))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(root, save_dir)
   file_io.delete_file(self._vocab_path)
   self.assertAllClose(
       {"output_0": [2, 0]},
       _import_and_infer(save_dir, {"keys": ["gamma", "alpha"]}))
   second_dir = os.path.join(self.get_temp_dir(), "second_dir")
   # Asset paths should track the location the SavedModel is loaded from.
   file_io.rename(save_dir, second_dir)
   self.assertAllClose(
       {"output_0": [2, 1]},
       _import_and_infer(second_dir, {"keys": ["gamma", "beta"]}))
  def testVariableSavedModel(self):
    """Test a basic model with Variables with saving/loading the SavedModel."""
    input_data = constant_op.constant(1., shape=[1])
    root = tracking.AutoTrackable()
    root.v1 = variables.Variable(3.)
    root.v2 = variables.Variable(2.)
    root.f = def_function.function(lambda x: root.v1 * root.v2 * x)
    to_save = root.f.get_concrete_function(input_data)

    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    save(root, save_dir, to_save)
    saved_model = load(save_dir)
    input_func = saved_model.signatures["serving_default"]

    variable_graph_def = input_func.graph.as_graph_def()
    self.assertTrue(self._hasStatefulPartitionedCallOp(variable_graph_def))

    output_func = convert_to_constants.convert_variables_to_constants_v2(
        input_func)
    constant_graph_def = output_func.graph.as_graph_def()
    self.assertEqual(0, self._getNumVariables(constant_graph_def))
    self.assertFalse(self._hasStatefulPartitionedCallOp(constant_graph_def))

    # Check value.
    expected_value = root.f(input_data)
    actual_value = self._evaluateGraphDef(constant_graph_def, input_func,
                                          [input_data.numpy()])
    self.assertEqual(expected_value.numpy(), actual_value)
Beispiel #6
0
  def test_function_with_captured_dataset(self):
    if test_util.is_gpu_available():
      self.skipTest("Currently broken when a GPU is available.")

    class HasDataset(module.Module):

      def __init__(self):
        super(HasDataset, self).__init__()
        self.dataset = (
            dataset_ops.Dataset.range(5)
            .map(lambda x: x ** 2))

      @def_function.function
      def __call__(self, x):
        current_sum = array_ops.zeros([], dtype=dtypes.int64)
        for element in self.dataset:
          current_sum += x * element
        return current_sum

    root = HasDataset()
    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    save.save(
        root, save_dir,
        signatures=root.__call__.get_concrete_function(
            tensor_spec.TensorSpec(None, dtypes.int64)))
    self.assertAllClose({"output_0": 3 * (1 + 4 + 9 + 16)},
                        _import_and_infer(save_dir, {"x": 3}))
Beispiel #7
0
 def test_non_concrete_error(self):
   root = tracking.AutoTrackable()
   root.f = def_function.function(lambda x: 2. * x)
   root.f(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(
       ValueError, "Expected a TensorFlow function"):
     save.save(root, save_dir, root.f)
Beispiel #8
0
 def test_non_concrete_error(self):
   root = tracking.AutoCheckpointable()
   root.f = def_function.function(lambda x: 2. * x)
   root.f(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(
       ValueError, "must be converted to concrete functions"):
     save.save(root, save_dir, root.f)
Beispiel #9
0
 def test_single_function_default_signature(self):
   model = tracking.AutoCheckpointable()
   model.f = def_function.function(lambda: 3., input_signature=())
   model.f()
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(model, save_dir)
   self.assertAllClose({"output_0": 3.},
                       _import_and_infer(save_dir, {}))
Beispiel #10
0
 def test_export_functional_keras_model(self):
   x = input_layer.Input((4,), name="x")
   y = core.Dense(4, name="out")(x)
   model = training.Model(x, y)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(model, save_dir)
   self.assertAllClose(
       {"out": model(array_ops.ones([1, 4]))},
       _import_and_infer(save_dir, {"x": [[1., 1., 1., 1.]]}))
Beispiel #11
0
 def test_nested_outputs(self):
   root = tracking.AutoCheckpointable()
   root.f = def_function.function(lambda x: (2. * x, (3. * x, 4. * x)))
   root.f(constant_op.constant(1.))
   to_save = root.f.get_concrete_function(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(
       ValueError, "non-flat outputs"):
     save.save(root, save_dir, to_save)
Beispiel #12
0
 def test_ambiguous_signatures(self):
   model = _ModelWithOptimizer()
   x = constant_op.constant([[3., 4.]])
   y = constant_op.constant([2.])
   model.call(x, y)
   model.second_function = def_function.function(lambda: 1.)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(ValueError, "call.*second_function"):
     save.save(model, save_dir)
Beispiel #13
0
 def test_single_method_default_signature(self):
   model = _ModelWithOptimizer()
   x = constant_op.constant([[3., 4.]])
   y = constant_op.constant([2.])
   model.call(x, y)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(model, save_dir)
   self.assertIn("loss",
                 _import_and_infer(save_dir,
                                   {"x": [[3., 4.]], "y": [2.]}))
Beispiel #14
0
 def test_no_reference_cycles(self):
   x = constant_op.constant([[3., 4.]])
   y = constant_op.constant([2.])
   self._model.call(x, y)
   if sys.version_info[0] < 3:
     # TODO(allenl): debug reference cycles in Python 2.x
     self.skipTest("This test only works in Python 3+. Reference cycles are "
                   "created in older Python versions.")
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(self._model, save_dir, self._model.call)
Beispiel #15
0
 def cycle(self, obj, cycles=1, signatures=None):
   to_save = obj
   # TODO(vbardiovsky): It would be nice if exported protos reached a fixed
   # point w.r.t. saving/restoring, ideally after 2nd saving.
   for _ in range(cycles):
     path = tempfile.mkdtemp(prefix=self.get_temp_dir())
     save.save(to_save, path, signatures)
     loaded = load.load(path)
     to_save = loaded
   return loaded
Beispiel #16
0
 def test_nested_dict_outputs(self):
   root = util.Checkpoint(
       f=def_function.function(
           lambda x: {"a": 2. * x, "b": (3. * x, 4. * x)}))
   root.f(constant_op.constant(1.))
   to_save = root.f.get_concrete_function(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   with self.assertRaisesRegexp(
       ValueError, "dictionary containing non-Tensor value"):
     save.save(root, save_dir, to_save)
Beispiel #17
0
 def test_method_save_signature(self):
   root = tracking.AutoCheckpointable()
   root.f = def_function.function(
       lambda x: 2. * x,
       input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])
   root.f(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(root, save_dir, root.f)
   self.assertEqual(
       {"output_0": 2.},
       _import_and_infer(save_dir, {"x": 1.}))
Beispiel #18
0
 def test_version_information_included(self):
   root = tracking.AutoTrackable()
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(root, save_dir)
   saved_model_proto = loader_impl.parse_saved_model(save_dir)
   self.assertEqual(
       versions.__version__,
       saved_model_proto.meta_graphs[0].meta_info_def.tensorflow_version)
   self.assertEqual(
       versions.__git_version__,
       saved_model_proto.meta_graphs[0].meta_info_def.tensorflow_git_version)
Beispiel #19
0
 def test_asset_path_returned(self):
   root = tracking.AutoTrackable()
   root.path = tracking.TrackableAsset(self._vocab_path)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   root.get_asset = def_function.function(lambda: root.path.asset_path)
   save.save(root, save_dir, signatures=root.get_asset.get_concrete_function())
   second_dir = os.path.join(self.get_temp_dir(), "second_dir")
   file_io.rename(save_dir, second_dir)
   imported_path = _import_and_infer(second_dir, {})["output_0"]
   self.assertIn(compat.as_str_any(second_dir),
                 compat.as_str_any(imported_path))
Beispiel #20
0
 def test_export_functional_keras_model_after_fit(self):
   x = input_layer.Input((1,))
   y = core.Dense(1, name="y")(x)
   model = training.Model(x, y)
   model.compile(optimizer="sgd", loss="mse")
   model.fit(x=numpy.array([[1.]]),
             y=numpy.array([2.]), epochs=2)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(model, save_dir)
   self.assertAllClose(
       {"y": model(constant_op.constant([[1.], [2.]]))},
       _import_and_infer(save_dir, {"input_1": [[1.], [2.]]}))
Beispiel #21
0
  def test_subclassed_no_signature(self):

    class Subclassed(training.Model):

      def call(self, inputs):
        return inputs * 2.

    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    model = Subclassed()
    with self.assertRaisesRegexp(
        ValueError, "no @tf.function-decorated methods"):
      save.save(model, save_dir)
Beispiel #22
0
  def test_unused_asset(self):
    root = tracking.AutoCheckpointable()
    root.f = def_function.function(
        lambda x: 2. * x,
        input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])
    root.asset = tracking.TrackableAsset(self._vocab_path)

    export_dir = os.path.join(self.get_temp_dir(), "save_dir")
    save.save(root, export_dir)
    self.assertAllClose(
        {"output_0": [0.2]},
        _import_and_infer(export_dir, {"x": [0.1]}))
Beispiel #23
0
 def test_optimizer(self):
   x = constant_op.constant([[3., 4.]])
   y = constant_op.constant([2.])
   model = _ModelWithOptimizer()
   first_loss = model.call(x, y)
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(model, save_dir, model.call)
   second_loss = model.call(x, y)
   self.assertNotEqual(first_loss, second_loss)
   self.assertAllClose(
       second_loss,
       _import_and_infer(save_dir, {"x": [[3., 4.]], "y": [2.]}))
Beispiel #24
0
  def test_no_signature(self):

    class Model(util.Checkpoint):

      def call(self, inputs):
        return inputs * 2.

    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    model = Model()
    with self.assertRaisesRegexp(
        ValueError, "no @tf.function-decorated methods"):
      save.save(model, save_dir)
Beispiel #25
0
 def test_variable(self):
   root = tracking.AutoCheckpointable()
   root.v1 = variables.Variable(3.)
   root.v2 = variables.Variable(2.)
   root.f = def_function.function(
       lambda x: root.v1 * root.v2 * x)
   root.f(constant_op.constant(1.))
   to_save = root.f.get_concrete_function(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(root, save_dir, to_save)
   self.assertAllEqual({"output_0": 12.},
                       _import_and_infer(save_dir, {"x": 2.}))
Beispiel #26
0
  def test_find_default_save_function(self):

    class ObjWithDefaultSignature(util.Checkpoint):

      @def_function.function(input_signature=[tensor_spec.TensorSpec(
          shape=None, dtype=dtypes.float32)])
      def _default_save_signature(self, x):
        return x + x + 1

    obj = ObjWithDefaultSignature()
    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    save.save(obj, save_dir)
    self.assertAllClose(
        {"output_0": 7.}, _import_and_infer(save_dir, {"x": 3.}))
Beispiel #27
0
 def test_sensible_graph_building_exception(self):
   root = util.Checkpoint(v=variables.Variable(2.))
   root.f = def_function.function(
       lambda x: 2. * root.v,
       input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])
   export_dir = os.path.join(self.get_temp_dir(), "save_dir")
   @def_function.function
   def _calls_save():
     save.save(root, export_dir)
   with self.assertRaisesRegexp(AssertionError, "tf.function"):
     _calls_save()
   with ops.Graph().as_default():
     with self.assertRaisesRegexp(AssertionError, "enable_eager_execution"):
       save.save(root, export_dir)
  def test_model_save(self):
    input_dim = 5
    model = testing_utils.get_small_mlp(10, 3, input_dim)
    inputs = array_ops.ones((8, 5))

    if testing_utils.get_model_type() == 'subclass':
      model._set_inputs(inputs)

    save_dir = os.path.join(self.get_temp_dir(), 'saved_model')
    save_lib.save(model, save_dir)

    self.assertAllClose(
        {model.output_names[0]: model.predict_on_batch(inputs)},
        _import_and_infer(save_dir, {model.input_names[0]: np.ones((8, 5))}))
Beispiel #29
0
  def test_docstring(self):

    class Adder(util.Checkpoint):

      @def_function.function(input_signature=[tensor_spec.TensorSpec(
          shape=None, dtype=dtypes.float32)])
      def add(self, x):
        return x + x + 1.

    to_save = Adder()
    to_save.add(constant_op.constant(1.))
    save_dir = os.path.join(self.get_temp_dir(), "saved_model")
    save.save(to_save, save_dir)
    self.assertAllClose({"output_0": 7.},
                        _import_and_infer(save_dir, {"x": 3.}))
Beispiel #30
0
 def test_method_save_concrete(self):
   root = tracking.AutoCheckpointable()
   root.f = def_function.function(
       lambda z: {"out": 2. * z})
   root.f(constant_op.constant(1.))
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   save.save(
       root,
       save_dir,
       {"non_default_key": root.f.get_concrete_function(
           tensor_spec.TensorSpec(None, dtypes.float32))})
   self.assertEqual(
       {"out": 2.},
       _import_and_infer(
           save_dir, {"z": 1.}, signature_key="non_default_key"))
Beispiel #31
0
    def test_default_attr_stripping(self):
        class Complex(util.Checkpoint):
            @def_function.function(input_signature=[])
            def __call__(self):
                return math_ops.complex(constant_op.constant(1.),
                                        constant_op.constant(2.),
                                        name="complex")

        to_save = Complex()
        to_save()
        save_dir = os.path.join(self.get_temp_dir(), "saved_model")
        save.save(to_save, save_dir)
        graph = ops.Graph()
        with graph.as_default(), self.session(graph) as session:
            loader.load(session, [tag_constants.SERVING], save_dir)
            func, = [
                f for name, f in graph._functions.items() if "call" in name
            ]
            complex_node, = [
                node for node in func.definition.node_def
                if node.op == "Complex"
            ]
            self.assertNotIn("T", complex_node.attr)
            self.assertNotIn("Tout", complex_node.attr)
Beispiel #32
0
    def test_assets_import(self):
        file1 = self._make_asset("contents 1")
        file2 = self._make_asset("contents 2")

        root = tracking.Checkpointable()
        root.f = def_function.function(
            lambda x: 2. * x,
            input_signature=[tensor_spec.TensorSpec(None, dtypes.float32)])
        root.asset1 = tracking.TrackableAsset(file1)
        root.asset2 = tracking.TrackableAsset(file2)

        save_dir = os.path.join(self.get_temp_dir(), "save_dir")
        save.save(root, save_dir)

        file_io.delete_file(file1)
        file_io.delete_file(file2)
        load_dir = os.path.join(self.get_temp_dir(), "load_dir")
        file_io.rename(save_dir, load_dir)

        imported = load.load(load_dir)
        with open(imported.asset1.asset_path.numpy(), "r") as f:
            self.assertEquals("contents 1", f.read())
        with open(imported.asset2.asset_path.numpy(), "r") as f:
            self.assertEquals("contents 2", f.read())
Beispiel #33
0
    def test_save_uninitialized_variable(self):
        root = tracking.AutoTrackable()
        root.uninitialized_variable = resource_variable_ops.UninitializedVariable(
            name="uninitialized_variable", dtype=dtypes.float32)
        root.initialized_variable = variables.Variable(
            1.0, name="initialized_variable")

        # TODO(b/149594077): Python loading does not work now partly because it
        # shouldn't, as the public API and semantics of uninitialized variables
        # are not properly defined, and officially supporting loading would end up
        # defining semantics "by usage." We should only allow loading once the API
        # is made official.
        export_dir = os.path.join(self.get_temp_dir(), "saved_model")
        save.save(root, export_dir)
        with self.assertRaisesRegex(FileNotFoundError,
                                    "Key uninitialized_variable"):
            load.load(export_dir)
        with ops.Graph().as_default(), session_lib.Session() as session:
            # The final ValueError here (with "no variables to save") is confusing,
            # but errors upstream give the user the correct information (a
            # NotFoundError stating that the uninitalized_variable was not found in
            # the checkpoint).
            with self.assertRaises(ValueError):
                loader.load(session, [tag_constants.SERVING], export_dir)
Beispiel #34
0
    def test_model_save_and_load(self):
        input_arr = np.random.random((1, 3)).astype(np.float32)
        target_arr = np.random.random((1, 4)).astype(np.float32)

        model = testing_utils.get_small_mlp(1, 4, input_dim=3)
        model.layers[-1].activity_regularizer = regularizers.get('l2')
        model.activity_regularizer = regularizers.get('l2')
        model.compile(loss='mse', optimizer='rmsprop')
        model.train_on_batch(input_arr, target_arr)

        def callable_loss():
            return math_ops.reduce_sum(model.weights[0])

        model.add_loss(callable_loss)
        saved_model_dir = self._save_model_dir()
        tf_save.save(model, saved_model_dir)
        loaded = keras_load.load(saved_model_dir)
        self.evaluate(variables.variables_initializer(loaded.variables))
        self.assertAllClose(self.evaluate(model.weights),
                            self.evaluate(loaded.weights))

        input_arr = constant_op.constant(
            np.random.random((1, 3)).astype(np.float32))
        self.assertAllClose(self.evaluate(model(input_arr)),
                            self.evaluate(loaded(input_arr)))
        # Validate losses. The order of conditional losses may change between the
        # model and loaded model, so sort the losses first.
        if context.executing_eagerly():
            self.assertAllClose(sorted(self.evaluate(model.losses)),
                                sorted(self.evaluate(loaded.losses)))
        else:
            self.assertAllClose(self.evaluate(model.get_losses_for(None)),
                                self.evaluate(loaded.get_losses_for(None)))
            self.assertAllClose(
                sorted(self.evaluate(model.get_losses_for(input_arr))),
                sorted(self.evaluate(loaded.get_losses_for(input_arr))))
Beispiel #35
0
    def testSignatures(self):
        """Test values for `signature_keys` argument."""
        root = self._getSimpleVariableModel()
        input_data = constant_op.constant(1., shape=[1])
        to_save = root.f.get_concrete_function(input_data)

        save_dir = os.path.join(self.get_temp_dir(), 'saved_model')
        save(root, save_dir, to_save)

        # Convert model with invalid `signature_keys`.
        with self.assertRaises(ValueError) as error:
            _ = lite.TFLiteConverterV2.from_saved_model(
                save_dir, signature_keys=['INVALID'])
        self.assertIn("Invalid signature key 'INVALID'", str(error.exception))

        # Convert model with empty `signature_keys`.
        converter = lite.TFLiteConverterV2.from_saved_model(save_dir,
                                                            signature_keys=[])
        tflite_model = converter.convert()

        # Check values from converted model.
        expected_value = root.f(input_data)
        actual_value = self._evaluateTFLiteModel(tflite_model, [input_data])
        self.assertEqual(expected_value.numpy(), actual_value)
Beispiel #36
0
  def testTrtGraphConverter_AllowEngineNativeSegmentExecution(self):

    # This test will not work anymore with TRT >= 8. TensorRT does not
    # preallocate anymore the max_workspace_size_bytes, but rather allocates as
    # it needs up to this value.
    # TODO: update the unittest to make this TRTEngine creation fail with TRT8.
    if trt_utils.is_linked_tensorrt_version_greater_equal(8, 0, 0):
      return

    np_input1, np_input2 = self._RandomInput([4, 1, 1])

    # Create a model and save it.
    input_saved_model_dir = self.mkdtemp()
    root = self._GetModelForV2()
    save.save(root, input_saved_model_dir,
              {_SAVED_MODEL_SIGNATURE_KEY: root.run})

    def _InputFn():
      yield np_input1, np_input2

    # Run TRT conversion and request an unreasonably large workspace.
    converter = self._CreateConverterV2(
        input_saved_model_dir, max_workspace_size_bytes=10 << 40)
    converter.convert()

    os.environ["TF_TRT_ALLOW_ENGINE_NATIVE_SEGMENT_EXECUTION"] = "False"
    with self.assertRaisesRegex(
        errors.AbortedError,
        r"User disallowed engine native segment execution"):
      try:
        converter.build(input_fn=_InputFn)
      finally:
        # Always reset the environment variable.
        os.environ["TF_TRT_ALLOW_ENGINE_NATIVE_SEGMENT_EXECUTION"] = "True"

    converter.build(input_fn=_InputFn)
Beispiel #37
0
 def test_single_function_no_signature(self):
     model = tracking.AutoTrackable()
     model.f = def_function.function(lambda: 3.)
     save_dir = os.path.join(self.get_temp_dir(), "saved_model")
     save.save(model, save_dir)
    def test_model_with_uncalibrated_subgraph(self):
        class IfModel(module.Module):
            @def_function.function(input_signature=[
                tensor_spec.TensorSpec(shape=[1, 4], dtype=dtypes.float32)
            ])
            def model_fn(self, x):
                if math_ops.reduce_sum(x) > 10.0:
                    filters = np.random.uniform(low=-1.0,
                                                high=1.0,
                                                size=(4, 3)).astype('f4')
                    bias = np.random.uniform(low=-1.0, high=1.0,
                                             size=(3, )).astype('f4')
                    out = math_ops.matmul(x, filters)
                    out = nn_ops.bias_add(out, bias)
                    return {'output': out}

                filters = np.random.uniform(low=-1.0, high=1.0,
                                            size=(4, 3)).astype('f4')
                bias = np.random.uniform(low=-1.0, high=1.0,
                                         size=(3, )).astype('f4')
                out = math_ops.matmul(x, filters)
                out = nn_ops.bias_add(out, bias)
                return {'output': out}

        model = IfModel()
        input_saved_model_path = self.create_tempdir('input').full_path
        saved_model_save.save(model, input_saved_model_path)

        def data_gen():
            for _ in range(10):
                yield {
                    'x':
                    ops.convert_to_tensor(
                        np.random.uniform(low=0.0, high=1.0,
                                          size=(1, 4)).astype('f4')),
                }

        tags = [tag_constants.SERVING]
        output_directory = self.create_tempdir().full_path

        quantization_options = quant_opts_pb2.QuantizationOptions(
            quantization_method=quant_opts_pb2.QuantizationMethod(
                experimental_method=_ExperimentalMethod.STATIC_RANGE))

        with warnings.catch_warnings(record=True) as warnings_list:
            converted_model = quantize_model.quantize(
                input_saved_model_path, ['serving_default'],
                tags,
                output_directory,
                quantization_options,
                representative_dataset=data_gen)

            self.assertNotEmpty(warnings_list)

            # Warning message should contain the function name. The uncalibrated path
            # is when the condition is true, so 'cond_true' function must be part of
            # the warning message.
            self.assertTrue(
                self._any_warning_contains('cond_true', warnings_list))
            self.assertFalse(
                self._any_warning_contains('cond_false', warnings_list))
            self.assertTrue(
                self._any_warning_contains('does not have min or max values',
                                           warnings_list))

        self.assertIsNotNone(converted_model)
        self.assertEqual(list(converted_model.signatures._signatures.keys()),
                         ['serving_default'])
        output_loader = saved_model_loader.SavedModelLoader(output_directory)
        output_meta_graphdef = output_loader.get_meta_graph_def_from_tags(tags)
        self.assertTrue(
            _contains_quantized_function_call(output_meta_graphdef))
Beispiel #39
0
 def test_unbuilt_model_does_not_prevent_saving(self):
     root = util.Checkpoint(model=sequential.Sequential([core.Dense(2)]))
     save.save(root, os.path.join(self.get_temp_dir(), "saved_model"))
  def test_model_export_cpu(self):
    strategy = self._get_strategy()

    with strategy.scope():
      first_mid_level_contents = np.ones((4, 4))
      first_mid_level_optimizer = tpu_embedding_v2_utils.SGD(learning_rate=0.1)
      initializer = init_ops_v2.Constant(first_mid_level_contents)

      table = tpu_embedding_v2_utils.TableConfig(
          vocabulary_size=4,
          dim=4,
          initializer=initializer,
          combiner='sum',
          name='table')
      feature_config = (tpu_embedding_v2_utils.FeatureConfig(
          table=table, name='feature'),)

      first_mid_level = tpu_embedding_v1.TPUEmbeddingV0(
          feature_config, first_mid_level_optimizer)

      first_mid_level.build()

    cpu_mid_level_optimizer = tpu_embedding_v2_utils.SGD(learning_rate=0.1)
    cpu_mid_level = tpu_embedding_for_serving.TPUEmbeddingForServing(
        feature_config, cpu_mid_level_optimizer)

    cpu_mid_level.build()

    tpu_checkpoint = util.Checkpoint(model=first_mid_level)
    tpu_checkpoint.save(self._get_tmpdir('export_cpu', 'save'))

    # We restore the checkpoint of our tpu mid level onto our cpu mid level.
    cpu_checkpoint = util.Checkpoint(model=cpu_mid_level)
    cpu_checkpoint.restore(self._get_tmpdir('export_cpu', 'save-1'))

    @def_function.function
    def serve_tensors(features):
      features = tpu_embedding_for_serving.cpu_embedding_lookup(
          features, None, cpu_mid_level.embedding_tables,
          cpu_mid_level._feature_config)
      return features[0]

    signatures = {
        'serving_default':
            serve_tensors.get_concrete_function((tensor_spec.TensorSpec(
                shape=(2,), dtype=dtypes.int32, name='feature'),))
    }
    save.save(
        cpu_mid_level,
        export_dir=self._get_tmpdir('export_cpu', 'exported_model'),
        signatures=signatures)

    imported = load.load(self._get_tmpdir('export_cpu', 'exported_model'))
    predict_fn = imported.signatures['serving_default']

    input_feature_value = np.array([1, 0])
    input_batch = (constant_op.constant(
        input_feature_value, dtype=dtypes.int32),)
    prediction = predict_fn(*input_batch)['output_0']
    self.assertAllClose(prediction.numpy(),
                        first_mid_level_contents[input_feature_value])
Beispiel #41
0
 def _save_and_load(self, model):
   saved_model_dir = self._save_model_dir()
   tf_save.save(model, saved_model_dir)
   loaded = keras_load.load(saved_model_dir)
   return loaded
Beispiel #42
0
  def testShowAllWithFunctions(self):

    class DummyModel(tracking.AutoTrackable):
      """Model with callable polymorphic functions specified."""

      @def_function.function
      def func1(self, a, b, c):
        if c:
          return a + b
        else:
          return a * b

      @def_function.function(input_signature=[
          tensor_spec.TensorSpec(shape=(2, 2), dtype=dtypes.float32)
      ])
      def func2(self, x):
        return x + 2

      @def_function.function
      def __call__(self, y, c=7):
        return y + 2 * c

    saved_model_dir = os.path.join(test.get_temp_dir(), 'dummy_model')
    dummy_model = DummyModel()
    # Call with specific values to create new polymorphic function traces.
    dummy_model.func1(constant_op.constant(5), constant_op.constant(9), True)
    dummy_model(constant_op.constant(5))
    save.save(dummy_model, saved_model_dir)
    self.parser = saved_model_cli.create_parser()
    args = self.parser.parse_args(['show', '--dir', saved_model_dir, '--all'])
    with captured_output() as (out, err):
      saved_model_cli.show(args)
    output = out.getvalue().strip()
    exp_out = """MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: NoOp
  Method name is: 

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['x'] tensor_info:
        dtype: DT_FLOAT
        shape: (2, 2)
        name: serving_default_x:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['output_0'] tensor_info:
        dtype: DT_FLOAT
        shape: (2, 2)
        name: PartitionedCall:0
  Method name is: tensorflow/serving/predict

Defined Functions:
  Function Name: '__call__'
    Option #1
      Callable with:
        Argument #1
          y: TensorSpec(shape=(), dtype=tf.int32, name='y')
        Argument #2
          DType: int
          Value: 7

  Function Name: 'func1'
    Option #1
      Callable with:
        Argument #1
          a: TensorSpec(shape=(), dtype=tf.int32, name='a')
        Argument #2
          b: TensorSpec(shape=(), dtype=tf.int32, name='b')
        Argument #3
          DType: bool
          Value: True

  Function Name: 'func2'
    Option #1
      Callable with:
        Argument #1
          x: TensorSpec(shape=(2, 2), dtype=tf.float32, name='x')
""".strip()  # pylint: enable=line-too-long
    self.maxDiff = None  # Produce a useful error msg if the comparison fails
    self.assertMultiLineEqual(output, exp_out)
    self.assertEqual(err.getvalue().strip(), '')
Beispiel #43
0
    def testTrtGraphConverter_BasicConversion_v2(self):
        """Test case for trt_convert.TrtGraphConverter()."""
        if not is_tensorrt_enabled():
            return

        np_input = np.random.random_sample([4, 1, 1]).astype(np.float32)

        # Create a model and save it.
        input_saved_model_dir = tempfile.mkdtemp(dir=self.get_temp_dir())
        root = self._GetModelForV2()
        expected_output = root.run(np_input)
        save.save(root, input_saved_model_dir,
                  {_SAVED_MODEL_SIGNATURE_KEY: root.run})

        # Run TRT conversion.
        converter = trt_convert.TrtGraphConverterV2(
            input_saved_model_dir=input_saved_model_dir,
            input_saved_model_signature_key=_SAVED_MODEL_SIGNATURE_KEY,
            conversion_params=trt_convert.DEFAULT_TRT_CONVERSION_PARAMS.
            _replace(precision_mode=trt_convert.TrtPrecisionMode.FP32,
                     is_dynamic_op=True,
                     maximum_cached_engines=2,
                     use_function_backup=False))
        converted_func = converter.convert()

        def _check_trt_ops(graph_def):
            trt_op_names = [
                node.name for node in graph_def.node
                if node.op == "TRTEngineOp"
            ]
            for func in graph_def.library.function:
                for node in func.node_def:
                    if node.op == "TRTEngineOp":
                        trt_op_names.append(node.name)
            self.assertEqual(1, len(trt_op_names))
            self.assertIn("TRTEngineOp_0", trt_op_names[0])

        # Verify the converted GraphDef and ConcreteFunction.
        self.assertIsInstance(converted_func, def_function.Function)
        converted_concrete_func = converted_func.get_concrete_function(
            tensor_spec.TensorSpec(shape=[None, 1, 1], dtype=dtypes.float32))
        _check_trt_ops(converted_concrete_func.graph.as_graph_def())

        # Save the converted model without any TRT engine cache.
        output_saved_model_dir = tempfile.mkdtemp(dir=self.get_temp_dir())
        converter.save(output_saved_model_dir)
        unexpected_asset_file = os.path.join(
            output_saved_model_dir,
            "assets/trt-serialized-engine.TRTEngineOp_0")
        self.assertFalse(os.path.exists(unexpected_asset_file))

        # Run the converted function to populate the engine cache.
        output_with_trt = converted_func(np_input)
        self.assertEqual(1, len(output_with_trt))
        self.assertAllClose(expected_output,
                            output_with_trt[0],
                            atol=1e-6,
                            rtol=1e-6)

        # Save the converted model again with serialized engine cache.
        output_saved_model_dir = tempfile.mkdtemp(dir=self.get_temp_dir())
        converter.save(output_saved_model_dir)
        expected_asset_file = os.path.join(
            output_saved_model_dir,
            "assets/trt-serialized-engine.TRTEngineOp_0")
        self.assertTrue(os.path.exists(expected_asset_file))
        self.assertTrue(os.path.getsize(expected_asset_file))

        # Load and verify the converted model.
        #
        # TODO(laigd): the name of then new input_signature of the
        # `root_with_trt.run` function is empty string (originaly was None),
        # investigate why.
        root_with_trt = load.load(output_saved_model_dir)
        # TODO(laigd): `root_with_trt.run` is still using the original graph without
        # trt. Consider changing that.
        # _check_trt_ops(
        #     root_with_trt.run.get_concrete_function().graph.as_graph_def())
        converted_signature = root_with_trt.signatures[
            _SAVED_MODEL_SIGNATURE_KEY]
        _check_trt_ops(converted_signature.graph.as_graph_def())
        output_with_trt = converted_signature(ops.convert_to_tensor(np_input))
        # The output of running the converted signature is a dict due to
        # compatibility reasons with V1 SavedModel signature mechanism.
        output_with_trt = output_with_trt[output_with_trt.keys()[0]]
        self.assertAllClose(expected_output,
                            output_with_trt,
                            atol=1e-6,
                            rtol=1e-6)
Beispiel #44
0
    def test_save_variable_devices(self, save_devices, meta_graph_only):
        context._reset_context()
        cpus = context.context().list_physical_devices("CPU")
        if len(cpus) == 1:
            context.context().set_logical_device_configuration(
                cpus[0], [
                    context.LogicalDeviceConfiguration(),
                    context.LogicalDeviceConfiguration()
                ])
        context.ensure_initialized()

        root = tracking.AutoTrackable()
        with ops.device("CPU:0"):
            root.v0 = variables.Variable(1., name="v0")
        with ops.device("CPU:1"):
            root.v1 = variables.Variable(1., name="v1")

        options = save_options.SaveOptions(
            experimental_variable_policy=save_devices)
        file_name = os.path.join(self.get_temp_dir(), "saved_model")
        if meta_graph_only:
            save.export_meta_graph(obj=root,
                                   filename=file_name,
                                   options=options)
        else:
            save.save(obj=root, export_dir=file_name, options=options)

        meta = None
        if meta_graph_only:
            meta = meta_graph.read_meta_graph_file(file_name)
        else:
            meta = loader_impl.parse_saved_model(file_name).meta_graphs[0]

        # Check devices in meta graph nodes.
        graph_def = meta.graph_def
        v0 = next((n for n in graph_def.node if n.name == "v0"), None)
        v1 = next((n for n in graph_def.node if n.name == "v1"), None)
        self.assertIsNotNone(v0)
        self.assertIsNotNone(v1)
        if save_devices == save_options.VariablePolicy.SAVE_VARIABLE_DEVICES:
            self.assertIn("CPU:0", v0.device)
            self.assertIn("CPU:1", v1.device)
        else:
            self.assertEmpty(v0.device)
            self.assertEmpty(v1.device)

        # Check devices in object graph nodes.
        object_graph_def = meta.object_graph_def
        v0 = next((n.variable for n in object_graph_def.nodes
                   if n.HasField("variable") and n.variable.name == "v0"),
                  None)
        v1 = next((n.variable for n in object_graph_def.nodes
                   if n.HasField("variable") and n.variable.name == "v1"),
                  None)
        self.assertIsNotNone(v0)
        self.assertIsNotNone(v1)
        if save_devices == save_options.VariablePolicy.SAVE_VARIABLE_DEVICES:
            self.assertIn("CPU:0", v0.device)
            self.assertIn("CPU:1", v1.device)
        else:
            self.assertEmpty(v0.device)
            self.assertEmpty(v1.device)
Beispiel #45
0
 def cycle(self, obj):
   path = tempfile.mkdtemp(prefix=self.get_temp_dir())
   save.save(obj, path, signatures={})
   return load.load(path)
Beispiel #46
0
def export_saved_model(model,
                       saved_model_path,
                       custom_objects=None,
                       as_text=False,
                       input_signature=None,
                       serving_only=False):
  """Exports a `tf.keras.Model` as a Tensorflow SavedModel.

  Note that at this time, subclassed models can only be saved using
  `serving_only=True`.

  The exported `SavedModel` is a standalone serialization of Tensorflow objects,
  and is supported by TF language APIs and the Tensorflow Serving system.
  To load the model, use the function
  `tf.keras.experimental.load_from_saved_model`.

  The `SavedModel` contains:

  1. a checkpoint containing the model weights.
  2. a `SavedModel` proto containing the Tensorflow backend graph. Separate
     graphs are saved for prediction (serving), train, and evaluation. If
     the model has not been compiled, then only the graph computing predictions
     will be exported.
  3. the model's json config. If the model is subclassed, this will only be
     included if the model's `get_config()` method is overwritten.

  Example:

  ```python
  import tensorflow as tf

  # Create a tf.keras model.
  model = tf.keras.Sequential()
  model.add(tf.keras.layers.Dense(1, input_shape=[10]))
  model.summary()

  # Save the tf.keras model in the SavedModel format.
  path = '/tmp/simple_keras_model'
  tf.keras.experimental.export_saved_model(model, path)

  # Load the saved keras model back.
  new_model = tf.keras.experimental.load_from_saved_model(path)
  new_model.summary()
  ```

  Args:
    model: A `tf.keras.Model` to be saved. If the model is subclassed, the flag
      `serving_only` must be set to True.
    saved_model_path: a string specifying the path to the SavedModel directory.
    custom_objects: Optional dictionary mapping string names to custom classes
      or functions (e.g. custom loss functions).
    as_text: bool, `False` by default. Whether to write the `SavedModel` proto
      in text format. Currently unavailable in serving-only mode.
    input_signature: A possibly nested sequence of `tf.TensorSpec` objects, used
      to specify the expected model inputs. See `tf.function` for more details.
    serving_only: bool, `False` by default. When this is true, only the
      prediction graph is saved.

  Raises:
    NotImplementedError: If the model is a subclassed model, and serving_only is
      False.
    ValueError: If the input signature cannot be inferred from the model.
    AssertionError: If the SavedModel directory already exists and isn't empty.
  """
  warnings.warn('`tf.keras.experimental.export_saved_model` is deprecated'
                'and will be removed in a future version. '
                'Please use `model.save(..., save_format="tf")` or '
                '`tf.keras.models.save_model(..., save_format="tf")`.')
  if serving_only:
    save_lib.save(
        model,
        saved_model_path,
        signatures=saving_utils.trace_model_call(model, input_signature))
  else:
    _save_v1_format(model, saved_model_path, custom_objects, as_text,
                    input_signature)

  try:
    _export_model_json(model, saved_model_path)
  except NotImplementedError:
    logging.warning('Skipped saving model JSON, subclassed model does not have '
                    'get_config() defined.')
  def save(self, output_saved_model_dir):
    """Save the converted SavedModel.

    Args:
      output_saved_model_dir: directory to saved the converted SavedModel.
    """
    assert self._converted

    # Serialize the TRT engines in the cache if any, and create trackable
    # resource to track them.
    engine_asset_dir = tempfile.mkdtemp()
    resource_map = {}

    def _serialize_and_track_engine(canonical_engine_name):
      """Serialize TRT engines in the cache and track them."""
      # Don't dump the same cache twice.
      if canonical_engine_name in resource_map:
        return

      filename = os.path.join(engine_asset_dir,
                              "trt-serialized-engine." + canonical_engine_name)
      try:
        gen_trt_ops.dump_trt_engine_cache(
            container=_TRT_ENGINE_CACHE_CONTAINER_NAME,
            resource_name=canonical_engine_name,
            filename=filename,
            delete_cache_after_dump=True)
      except errors.NotFoundError:
        # If user haven't run the function to populate the engine, it's fine,
        # and we don't need to track any serialized TRT engines.
        return

      resource_map[canonical_engine_name] = TRTEngineResource(
          canonical_engine_name, filename,
          self._conversion_params.maximum_cached_engines)

    # Remove all scope prefixes in the node name. In TF 2.0, the same concrete
    # function can be initialized multiple times with different prefixes, and
    # this will result in the same TRTEngineOp being initialized multiple times
    # with different cache and duplicate TRT engines.
    # TODO(laigd): this may be caused by the fact that TRTEngineOp is not
    # stataful, need to investigate.
    # TODO(laigd): we rely on the fact that all functions are fully inlined
    # before TF-TRT optimizer is called, as otherwise it may generate the same
    # name when optimizing a different function graph. Fix this.
    canonical_engine_name = lambda node: node.name.split("/")[-1]
    for node in self._converted_graph_def.node:
      if node.op == _TRT_ENGINE_OP_NAME:
        _serialize_and_track_engine(canonical_engine_name(node))
    for func in self._converted_graph_def.library.function:
      for node in func.node_def:
        if node.op == _TRT_ENGINE_OP_NAME:
          _serialize_and_track_engine(canonical_engine_name(node))

    self._saved_model.trt_engine_resources = resource_map

    # Rewrite the signature map using the optimized ConcreteFunction.
    signatures = {
        key: value for key, value in self._saved_model.signatures.items()
    }
    signatures[self._input_saved_model_signature_key] = self._converted_func
    save.save(self._saved_model, output_saved_model_dir, signatures)
  def testTrtGraphConverter_Int8Conversion_v2(self):
    if not is_tensorrt_enabled():
      return

    np_input1, np_input2 = self._RandomInput([4, 1, 1])

    # Create a model and save it.
    input_saved_model_dir = tempfile.mkdtemp(dir=self.get_temp_dir())
    root = self._GetModelForV2()
    expected_output = root.run(np_input1, np_input2)
    save.save(root, input_saved_model_dir,
              {_SAVED_MODEL_SIGNATURE_KEY: root.run})

    # Run TRT conversion.
    converter = self._CreateConverterV2(
        input_saved_model_dir,
        precision_mode=trt_convert.TrtPrecisionMode.INT8,
        maximum_cached_engines=3)

    # Convert and perform INT8 calibration
    def _CalibrationInputFn():
      yield np_input1, np_input2

    converter.convert(calibration_input_fn=_CalibrationInputFn)

    trt_engine_name = self._GetUniqueTRTEngineOp(
        converter._converted_graph_def).name

    def _CheckFn(node):
      self.assertTrue(len(node.attr["calibration_data"].s), node.name)

    # Verify the converted GraphDef.
    self._CheckTrtOps(converter._converted_func, _CheckFn)  # pylint: disable=protected-access

    # Build another engine with different batch size.
    def _InputFn():
      yield self._RandomInput([5, 1, 1])

    converter.build(input_fn=_InputFn)

    # Save the converted model.
    # TODO(laigd): check that it should contain two engines.
    output_saved_model_dir = self.mkdtemp()
    converter.save(output_saved_model_dir)
    expected_asset_file = os.path.join(
        output_saved_model_dir,
        "assets/trt-serialized-engine." + trt_engine_name)
    self.assertTrue(os.path.exists(expected_asset_file))
    self.assertTrue(os.path.getsize(expected_asset_file))

    del converter
    gc.collect()  # Force GC to destroy the TRT engine cache.

    # Load and verify the converted model.
    root_with_trt = load.load(output_saved_model_dir)
    converted_signature = root_with_trt.signatures[_SAVED_MODEL_SIGNATURE_KEY]
    self._CheckTrtOps(converted_signature, _CheckFn)
    output_with_trt = converted_signature(
        inp1=ops.convert_to_tensor(np_input1),
        inp2=ops.convert_to_tensor(np_input2))
    self.assertEqual(1, len(output_with_trt))
    # The output of running the converted signature is a dict due to
    # compatibility reasons with V1 SavedModel signature mechanism.
    self.assertAllClose(
        expected_output,
        list(output_with_trt.values())[0],
        atol=1e-6,
        rtol=1e-6)

    # Run with an input of different batch size. It should build a new engine
    # using calibration table.
    # TODO(laigd): check that it should contain three engines.
    np_input1, np_input2 = self._RandomInput([6, 1, 1])
    converted_signature(
        inp1=ops.convert_to_tensor(np_input1),
        inp2=ops.convert_to_tensor(np_input2))

    del root_with_trt
    gc.collect()  # Force GC to destroy the TRT engine cache.
  def testTrtGraphConverter_DynamicConversion_v2(self):
    """Test case for trt_convert.TrtGraphConverter()."""
    if not is_tensorrt_enabled():
      return

    np_input1, np_input2 = self._RandomInput([4, 1, 1])

    # Create a model and save it.
    input_saved_model_dir = self.mkdtemp()
    root = self._GetModelForV2()
    expected_output = root.run(np_input1, np_input2)
    save.save(root, input_saved_model_dir,
              {_SAVED_MODEL_SIGNATURE_KEY: root.run})

    # Run TRT conversion.
    converter = self._CreateConverterV2(input_saved_model_dir)
    converter.convert()

    # Verify the converted GraphDef and ConcreteFunction.
    self._CheckTrtOps(converter._converted_func)  # pylint: disable=protected-access

    trt_engine_name = self._GetUniqueTRTEngineOp(
        converter._converted_graph_def).name

    # Save the converted model without any TRT engine cache.
    output_saved_model_dir = self.mkdtemp()
    converter.save(output_saved_model_dir)
    unexpected_asset_file = os.path.join(
        output_saved_model_dir,
        "assets/trt-serialized-engine." + trt_engine_name)
    self.assertFalse(os.path.exists(unexpected_asset_file))

    # Run the converted function to populate the engine cache.
    def _InputFn():
      yield np_input1, np_input2

    converter.build(input_fn=_InputFn)

    # Save the converted model again with serialized engine cache.
    output_saved_model_dir = self.mkdtemp()
    converter.save(output_saved_model_dir)
    expected_asset_file = os.path.join(
        output_saved_model_dir,
        "assets/trt-serialized-engine." + trt_engine_name)
    self.assertTrue(os.path.exists(expected_asset_file))
    self.assertTrue(os.path.getsize(expected_asset_file))

    del converter
    gc.collect()  # Force GC to destroy the TRT engine cache.

    # Load and verify the converted model.
    #
    # TODO(laigd): the name of the new input_signature of the
    # `root_with_trt.run` function is empty string (originally was None),
    # investigate why.
    root_with_trt = load.load(output_saved_model_dir)
    # TODO(laigd): `root_with_trt.run` is still using the original graph without
    # trt. Consider changing that.
    # self._CheckTrtOps(root_with_trt.run.get_concrete_function())
    converted_signature = root_with_trt.signatures[_SAVED_MODEL_SIGNATURE_KEY]
    self._CheckTrtOps(converted_signature)
    output_with_trt = converted_signature(
        inp1=ops.convert_to_tensor(np_input1),
        inp2=ops.convert_to_tensor(np_input2))
    # The output of running the converted signature is a dict due to
    # compatibility reasons with V1 SavedModel signature mechanism.
    self.assertAllClose(
        expected_output,
        list(output_with_trt.values())[0],
        atol=1e-6,
        rtol=1e-6)

    del root_with_trt
    gc.collect()  # Force GC to destroy the TRT engine cache.
 def test_must_restore_from_config_registration(self):
     layer = GlobalLayerThatShouldFailIfNotAdded()
     saved_model_dir = self._save_model_dir()
     tf_save.save(layer, saved_model_dir)
     _ = keras_load.load(saved_model_dir)
Beispiel #51
0
 def _calls_save():
     save.save(root, export_dir)
Beispiel #52
0
 def test_save_returns_none(self):
   # Test that `tf.saved_model.save` API returns None to user.
   root = tracking.AutoTrackable()
   save_dir = os.path.join(self.get_temp_dir(), "saved_model")
   result = save.save(root, save_dir)
   self.assertIsNone(result)
Beispiel #53
0
 def test_trivial_save_exception(self):
     save_dir = os.path.join(self.get_temp_dir(), "saved_model")
     with self.assertRaisesRegexp(ValueError, "signature"):
         save.save(tracking.Checkpointable(), save_dir)
    def test_qat_conv_model(self, activation_fn, has_bias):
        class ConvModel(module.Module):
            @def_function.function(input_signature=[
                tensor_spec.TensorSpec(name='input',
                                       shape=[1, 3, 4, 3],
                                       dtype=dtypes.float32),
                tensor_spec.TensorSpec(name='filter',
                                       shape=[2, 3, 3, 2],
                                       dtype=dtypes.float32),
            ])
            def conv(self, input_tensor, filter_tensor):
                q_input = array_ops.fake_quant_with_min_max_args(
                    input_tensor,
                    min=-0.1,
                    max=0.2,
                    num_bits=8,
                    narrow_range=False)
                q_filters = array_ops.fake_quant_with_min_max_args(
                    filter_tensor,
                    min=-1.0,
                    max=2.0,
                    num_bits=8,
                    narrow_range=False)
                bias = array_ops.constant([0, 0], dtype=dtypes.float32)
                out = nn_ops.conv2d(q_input,
                                    q_filters,
                                    strides=[1, 1, 2, 1],
                                    dilations=[1, 1, 1, 1],
                                    padding='SAME',
                                    data_format='NHWC')
                if has_bias:
                    out = nn_ops.bias_add(out, bias, data_format='NHWC')
                if activation_fn is not None:
                    out = activation_fn(out)
                q_out = array_ops.fake_quant_with_min_max_args(
                    out, min=-0.3, max=0.4, num_bits=8, narrow_range=False)
                return {'output': q_out}

        model = ConvModel()
        input_saved_model_path = self.create_tempdir('input').full_path
        saved_model_save.save(model, input_saved_model_path)

        signature_key = signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        tags = [tag_constants.SERVING]
        output_directory = self.create_tempdir().full_path

        quantization_options = quant_opts_pb2.QuantizationOptions(
            quantization_method=quant_opts_pb2.QuantizationMethod(
                experimental_method=_ExperimentalMethod.STATIC_RANGE))

        converted_model = quantize_model.quantize(input_saved_model_path,
                                                  [signature_key], tags,
                                                  output_directory,
                                                  quantization_options)
        self.assertIsNotNone(converted_model)
        self.assertEqual(list(converted_model.signatures._signatures.keys()),
                         [signature_key])

        input_data = np.random.uniform(low=-0.1, high=0.2,
                                       size=(1, 3, 4, 3)).astype('f4')
        filter_data = np.random.uniform(low=-0.5, high=0.5,
                                        size=(2, 3, 3, 2)).astype('f4')

        expected_outputs = model.conv(input_data, filter_data)
        got_outputs = converted_model.signatures[signature_key](
            input=ops.convert_to_tensor(input_data),
            filter=ops.convert_to_tensor(filter_data))
        # TODO(b/215633216): Check if the accuracy is acceptable.
        self.assertAllClose(expected_outputs, got_outputs, atol=0.01)

        output_loader = saved_model_loader.SavedModelLoader(output_directory)
        output_meta_graphdef = output_loader.get_meta_graph_def_from_tags(tags)
        self.assertTrue(
            _contains_quantized_function_call(output_meta_graphdef))
Beispiel #55
0
  def testShowAllWithPureConcreteFunction(self):

    class DummyModel(tracking.AutoTrackable):
      """Model with a callable concrete function."""

      def __init__(self):
        function = def_function.function(
            self.multiply,
            input_signature=[
                tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32),
                tensor_spec.TensorSpec(shape=(), dtype=dtypes.float32)
            ])
        self.pure_concrete_function = function.get_concrete_function()
        super(DummyModel, self).__init__()

      def multiply(self, a, b):
        return a * b

    saved_model_dir = os.path.join(test.get_temp_dir(), 'dummy_model')
    dummy_model = DummyModel()
    save.save(dummy_model, saved_model_dir)
    self.parser = saved_model_cli.create_parser()
    args = self.parser.parse_args(['show', '--dir', saved_model_dir, '--all'])
    with captured_output() as (out, err):
      saved_model_cli.show(args)
    output = out.getvalue().strip()
    exp_out = """MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: NoOp
  Method name is: 

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['a'] tensor_info:
        dtype: DT_FLOAT
        shape: ()
        name: serving_default_a:0
    inputs['b'] tensor_info:
        dtype: DT_FLOAT
        shape: ()
        name: serving_default_b:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['output_0'] tensor_info:
        dtype: DT_FLOAT
        shape: ()
        name: PartitionedCall:0
  Method name is: tensorflow/serving/predict

Defined Functions:
  Function Name: 'pure_concrete_function'
    Option #1
      Callable with:
        Argument #1
          a: TensorSpec(shape=(), dtype=tf.float32, name='a')
        Argument #2
          b: TensorSpec(shape=(), dtype=tf.float32, name='b')
""".strip()  # pylint: enable=line-too-long
    self.maxDiff = None  # Produce a useful error msg if the comparison fails
    self.assertMultiLineEqual(output, exp_out)
    self.assertEqual(err.getvalue().strip(), '')
    def test_ptq_model_with_variable(self):
        class ConvModelWithVariable(module.Module):
            """A simple model that performs a single convolution to the input tensor.

      It keeps the filter as a tf.Variable.
      """
            def __init__(self):
                self.filters = variables.Variable(
                    random_ops.random_uniform(shape=(2, 3, 3, 2),
                                              minval=-1.,
                                              maxval=1.))

            @def_function.function(input_signature=[
                tensor_spec.TensorSpec(name='input',
                                       shape=(1, 3, 4, 3),
                                       dtype=dtypes.float32),
            ])
            def __call__(self, x):
                out = nn_ops.conv2d(x,
                                    self.filters,
                                    strides=[1, 1, 2, 1],
                                    dilations=[1, 1, 1, 1],
                                    padding='SAME',
                                    data_format='NHWC')
                return {'output': out}

        def gen_data():
            for _ in range(255):
                yield {
                    'input':
                    random_ops.random_uniform(shape=(1, 3, 4, 3),
                                              minval=0,
                                              maxval=150)
                }

        model = ConvModelWithVariable()
        input_saved_model_path = self.create_tempdir('input').full_path
        saved_model_save.save(model, input_saved_model_path)

        signature_keys = [
            signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
        ]
        tags = {tag_constants.SERVING}
        output_directory = self.create_tempdir().full_path

        quantization_options = quant_opts_pb2.QuantizationOptions(
            quantization_method=quant_opts_pb2.QuantizationMethod(
                experimental_method=_ExperimentalMethod.STATIC_RANGE))

        converted_model = quantize_model.quantize(
            input_saved_model_path,
            signature_keys,
            tags,
            output_directory,
            quantization_options,
            representative_dataset=gen_data)

        self.assertIsNotNone(converted_model)
        self.assertEqual(list(converted_model.signatures._signatures.keys()),
                         signature_keys)

        output_loader = saved_model_loader.SavedModelLoader(output_directory)
        output_meta_graphdef = output_loader.get_meta_graph_def_from_tags(tags)
        self.assertTrue(
            _contains_quantized_function_call(output_meta_graphdef))
Beispiel #57
0
 def _create_save_v2_model(self):
     root = autotrackable.AutoTrackable()
     save_dir = os.path.join(self.get_temp_dir(), "saved_model")
     save.save(root, save_dir)
     self.addCleanup(shutil.rmtree, save_dir)
     return save_dir
    def test_depthwise_conv_ptq_model(self, activation_fn, has_bias, has_bn):
        class DepthwiseConvModel(module.Module):
            @def_function.function(input_signature=[
                tensor_spec.TensorSpec(shape=[1, 3, 4, 3],
                                       dtype=dtypes.float32)
            ])
            def conv(self, input_tensor):
                filters = np.random.uniform(low=-10,
                                            high=10,
                                            size=(2, 3, 3, 1)).astype('f4')
                bias = np.random.uniform(low=0, high=10, size=(3)).astype('f4')
                scale, offset = [1.0, 1.0, 1.0], [0.5, 0.5, 0.5]
                mean, variance = scale, offset
                out = nn_ops.depthwise_conv2d_native(input_tensor,
                                                     filters,
                                                     strides=[1, 2, 2, 1],
                                                     dilations=[1, 1, 1, 1],
                                                     padding='SAME',
                                                     data_format='NHWC')
                if has_bias:
                    out = nn_ops.bias_add(out, bias)
                if has_bn:
                    # Fusing is supported for non-training case.
                    out, _, _, _, _, _ = nn_ops.fused_batch_norm_v3(
                        out, scale, offset, mean, variance, is_training=False)
                if activation_fn is not None:
                    out = activation_fn(out)
                return {'output': out}

        model = DepthwiseConvModel()
        input_saved_model_path = self.create_tempdir('input').full_path
        saved_model_save.save(model, input_saved_model_path)

        def data_gen():
            for _ in range(255):
                yield {
                    'input_tensor':
                    ops.convert_to_tensor(
                        np.random.uniform(low=0, high=150,
                                          size=(1, 3, 4, 3)).astype('f4')),
                }

        tags = [tag_constants.SERVING]
        output_directory = self.create_tempdir().full_path

        quantization_options = quant_opts_pb2.QuantizationOptions(
            quantization_method=quant_opts_pb2.QuantizationMethod(
                experimental_method=_ExperimentalMethod.STATIC_RANGE))

        converted_model = quantize_model.quantize(
            input_saved_model_path, ['serving_default'],
            tags,
            output_directory,
            quantization_options,
            representative_dataset=data_gen)
        self.assertIsNotNone(converted_model)
        self.assertEqual(list(converted_model.signatures._signatures.keys()),
                         ['serving_default'])

        output_loader = saved_model_loader.SavedModelLoader(output_directory)
        output_meta_graphdef = output_loader.get_meta_graph_def_from_tags(tags)
        self.assertTrue(
            _contains_quantized_function_call(output_meta_graphdef))
        self.assertFalse(_contains_op(output_meta_graphdef,
                                      'FusedBatchNormV3'))
    def testUnsaveable(self, distribution, synchronization, aggregation, mode):
        if isinstance(
                distribution.extended,
                parameter_server_strategy.ParameterServerStrategyExtended):
            self.skipTest("n/a: not appliable to AggregatingVariable")
        if (isinstance(
                distribution,
                collective_all_reduce_strategy.CollectiveAllReduceStrategy)
                and mode == "graph"):
            self.skipTest(
                "MWMS combinations tests do not work well in graph mode.")
        if not distribution.extended._use_merge_call():
            self.skipTest("Unsupported combination.")
        with distribution.scope():
            v = variables_lib.Variable([1., 1.],
                                       synchronization=synchronization,
                                       aggregation=aggregation)

        with self.cached_session():
            self.evaluate(variables_lib.global_variables_initializer())

        export_dir = self.get_temp_dir()

        def _assert_unsaveable(f):
            # Ignore if it cannot be traced. Certain combinations are not supported or
            # yet or not allowed.
            try:
                f = def_function.function(f).get_concrete_function()
            except (NotImplementedError, ValueError):
                return
            with self.assertRaisesRegex(ValueError, "f_with_input_signature"):
                save.save(v, export_dir, signatures=f)

        _assert_unsaveable(lambda: v.assign(ops.convert_to_tensor([1., 1.])))
        _assert_unsaveable(
            lambda: v.assign_add(ops.convert_to_tensor([1., 1.])))
        _assert_unsaveable(
            lambda: v.assign_sub(ops.convert_to_tensor([1., 1.])))
        _assert_unsaveable(
            lambda: v.scatter_add(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_sub(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_mul(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_div(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_min(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_max(_make_index_slices([1.], [0])))
        _assert_unsaveable(
            lambda: v.scatter_update(_make_index_slices([1.], [0])))
        # Reading a ON_READ variable should be unsaveable if either:
        # 1) CollectiveAllReduceStrategy, and aggregation is MEAN/SUM.
        # 2) aggregation is SUM.
        if (synchronization == variables_lib.VariableSynchronization.ON_READ
                and
            (aggregation == variables_lib.VariableAggregation.SUM or
             (not distribution.extended._use_merge_call()) or
             (isinstance(
                 distribution.extended,
                 collective_all_reduce_strategy.CollectiveAllReduceExtended)
              and aggregation == variables_lib.VariableAggregation.MEAN))):
            _assert_unsaveable(v.read_value)
            _assert_unsaveable(v.value)
            _assert_unsaveable(lambda: ops.convert_to_tensor(v))
        else:
            # Otherwise reading a variable should be saveable.

            @def_function.function
            def f():
                v.read_value()
                v.value()
                return ops.convert_to_tensor(v)

            with self.cached_session():
                save.save(v, export_dir, signatures=f.get_concrete_function())
Beispiel #60
0
def export(model,
           saved_model_path,
           custom_objects=None,
           as_text=None,
           input_signature=None,
           serving_only=False):
    """Saves a `tf.keras.Model` into Tensorflow SavedModel format.

  `save_model` generates new files/folders under the `saved_model_path` folder:
  1) a checkpoint containing the model weights.
  2) a saved_model.pb file containing the model's MetaGraphs. The prediction
     graph is always exported. The evaluaton and training graphs are exported
     if the following conditions are met:
     - Evaluation: model loss is defined.
     - Training: model is compiled with an optimizer defined under `tf.train`.
       This is because `tf.keras.optimizers.Optimizer` instances cannot be
       saved to checkpoints.
  3) Model's json configuration, if model.get_config() has been implemented.
     This file can be used to reload the model using
     tf.keras.models.model_from_json(). Note that if any custom objects were
     used, they should be passed to the `custom_object` argument when loading
     the model.

  Model limitations:
  - Sequential and functional models can always be saved.
  - Subclassed models can only be saved when `serving_only=True`. This is due to
    the current implementation copying the model in order to export the training
    and evaluation graphs. Because the topology of subclassed models cannot be
    determined, the subclassed models cannot be cloned. Subclassed models will
    be entirely exportable in the future.

  Note that each mode is exported in separate graphs, so different modes do not
  share variables. To use the train graph with evaluation or prediction graphs,
  create a new checkpoint if variable values have been updated.

  Example:

  ```python
  import tensorflow as tf

  # Create a tf.keras model.
  model = tf.keras.Sequential()
  model.add(tf.keras.layers.Dense(1, input_shape=[10]))
  model.summary()

  # Save the tf.keras model in the SavedModel format.
  saved_to_path = tf.keras.experimental.export(
        model, '/tmp/my_simple_tf_keras_saved_model')

  # Load the saved keras model back.
  model_prime = tf.keras.experimental.load_from_saved_model(saved_to_path)
  model_prime.summary()
  ```

  Args:
    model: A `tf.keras.Model` to be saved. If the model is subclassed, the flag
      `serving_only` must be set to True.
    saved_model_path: a string specifying the path to the SavedModel directory.
      The SavedModel will be saved to a timestamped folder created within this
      directory.
    custom_objects: Optional dictionary mapping string names to custom classes
      or functions (e.g. custom loss functions).
    as_text: whether to write the `SavedModel` proto in text format. Currently
      unavailable in serving-only mode.
    input_signature: A possibly nested sequence of `tf.TensorSpec` objects, used
      to specify the expected model inputs. `input_signature`'s nested structure
      should match the expected nested structure of the inputs to the model. If
      this is not set, this function will attempt to infer the input shapes and
      dtypes from the model. Note that if the model is subclassed, the tensor
      inputs to the call function should be nested in the first argument (this
      is a general requirement for using subclassed models with Keras functions
      .fit(), .predict(), etc.).
    serving_only: Export only the outputs produced from calling the model in
      predict mode. The losses, optimizer, and other training configurations are
      not saved. If the SavedModel will only be used for serving (rather than
      retraining), or if the model is subclassed, this can be set to True.

  Returns:
    String path to the SavedModel folder, a subdirectory of `saved_model_path`.

  Raises:
    NotImplementedError: If the model is a subclassed model, and serving_only is
      False.
    ValueError: If the input signature cannot be inferred from the model.
  """
    export_dir = model_utils.get_timestamped_export_dir(saved_model_path)

    if serving_only:
        save_lib.save(model,
                      export_dir,
                      signatures=saving_utils.trace_model_call(
                          model, input_signature))
    else:
        _save_v1_format(model, export_dir, custom_objects, as_text,
                        input_signature)

    try:
        _export_model_json(model, export_dir)
    except NotImplementedError:
        logging.warning(
            'Skipped saving model JSON, subclassed model does not have '
            'get_config() defined.')

    return export_dir