jit.py 52.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

15 16
from __future__ import print_function

17 18
import os
import pickle
19
import warnings
20
import functools
21 22

import six
23
import paddle
24
from paddle.fluid import core
25 26
from paddle.fluid.compiler import BuildStrategy, CompiledProgram, ExecutionStrategy
from paddle.fluid.data_feeder import check_type
27
from paddle.fluid.dygraph.base import program_desc_tracing_guard, switch_to_static_graph
28
from paddle.fluid.dygraph.dygraph_to_static.logging_utils import set_code_level, set_verbosity
29
from paddle.fluid.dygraph.dygraph_to_static.program_translator import ProgramTranslator, StaticLayer, unwrap_decorators
30
from paddle.fluid.dygraph.io import EXTRA_VAR_INFO_FILENAME, VARIABLE_FILENAME, TranslatedLayer
31 32
from paddle.fluid.dygraph.layers import Layer
from paddle.fluid.executor import Executor, scope_guard
33 34 35
from paddle.fluid.framework import Block, ParamBase, Program, Variable
from paddle.fluid.framework import _current_expected_place, _dygraph_guard, _dygraph_tracer
from paddle.fluid.framework import dygraph_only, in_dygraph_mode
36
from paddle.fluid.wrapped_decorator import wrap_decorator
37

38 39
__all__ = [
    'TracedLayer', 'declarative', 'dygraph_to_static_func', 'set_code_level',
C
Chen Weihang 已提交
40
    'set_verbosity', 'save', 'load', 'SaveLoadConfig'
41
]
42 43 44 45 46 47 48 49 50 51 52 53


def create_program_from_desc(program_desc):
    program = Program()
    program.desc = program_desc
    program.blocks = [Block(program, 0)]
    program._sync_with_cpp()
    return program


def _extract_vars(inputs, result_list):
    if isinstance(inputs, Variable):
54
        result_list.append(inputs)
55
    elif isinstance(inputs, (list, tuple)):
56 57
        for var in inputs:
            _extract_vars(var, result_list)
58 59 60 61
    else:
        raise TypeError(
            "The type of 'each element of inputs' in fluid.dygraph.jit.TracedLayer.trace must be fluid.Variable, but received {}.".
            format(type(inputs)))
62 63 64 65 66 67 68 69


def extract_vars(inputs):
    result_list = []
    _extract_vars(inputs, result_list)
    return result_list


70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
def _dygraph_to_static_func_(dygraph_func):
    """
    Converts imperative dygraph APIs into declarative function APIs. Decorator
    @dygraph_to_static_func only converts imperative dygraph APIs into
    declarative net-building APIs, which means it doesn't return immediate
    digital result as imperative mode. Users should handle Program and Executor
    by themselves.

    Note:
    This decorator is NOT our recommended way to transform imperative function
    to declarative function. We will remove this decorator after we finalize
    cleaning up code.

    Args:
        dygraph_func (callable): callable imperative function.

    Returns:
        Callable: converting imperative dygraph APIs into declarative
        net-building APIs.

    Examples:
        .. code-block:: python

          import paddle.fluid as fluid
          import numpy as np
          from paddle.fluid.dygraph.jit import dygraph_to_static_func

          @dygraph_to_static_func
          def func(x):
              if fluid.layers.mean(x) < 0:
                  x_v = x - 1
              else:
                  x_v = x + 1

               return x_v

          x = fluid.layers.fill_constant(shape=[3, 3], value=0, dtype='float64')

          x_v = func(x)
          exe = fluid.Executor(fluid.CPUPlace())
          out = exe.run(fetch_list=[x_v])
          print(out[0])
          # [[1. 1. 1.]
          #  [1. 1. 1.]
          #  [1. 1. 1.]]

    """

    # TODO: remove this decorator after we finalize training API
119 120
    def __impl__(*args, **kwargs):
        program_translator = ProgramTranslator()
121
        if in_dygraph_mode() or not program_translator.enable_declarative:
122
            warnings.warn(
123
                "The decorator 'dygraph_to_static_func' doesn't work in "
124
                "dygraph mode or set ProgramTranslator.enable to False. "
125 126 127 128
                "We will just return dygraph output.")
            return dygraph_func(*args, **kwargs)
        static_func = program_translator.get_func(dygraph_func)
        return static_func(*args, **kwargs)
129 130 131 132

    return __impl__


133
dygraph_to_static_func = wrap_decorator(_dygraph_to_static_func_)
134

135

136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
def copy_decorator_attrs(original_func, decorated_obj):
    """
    Copies some necessary attributes from original function into decorated function.

    Args:
        original_func(callable): the original decorated function.
        decorated_obj(StaticLayer): the target decorated StaticLayer object.
    """
    decorator_name = "declarative"

    decorated_obj.__name__ = original_func.__name__
    decorated_obj._decorator_name = decorator_name
    decorated_obj.__wrapped__ = original_func
    decorated_obj.__doc__ = original_func.__doc__
    if hasattr(original_func, "__module__"):
        decorated_obj.__module__ = original_func.__module__

    return decorated_obj


def declarative(function=None, input_spec=None):
157 158 159
    """
    Converts imperative dygraph APIs into declarative function APIs. Decorator
    @declarative handles the Program and Executor of static mode and returns
160 161 162 163
    the result as dygraph Tensor(s). Users could use the returned dygraph
    Tensor(s) to do imperative training, inference, or other operations. If the
    decorated function calls other imperative function, the called one will be
    converted into declarative function as well.
164

165
    Args:
166 167 168
        function (callable): callable imperative function.
        input_spec(list[InputSpec]): list of InputSpec to specific the shape/dtype/name
            information of each input Tensor.
169

170
    Returns:
171
        Tensor(s): containing the numerical result.
172

173 174
    Examples:
        .. code-block:: python
175

176 177 178
          import paddle.fluid as fluid
          import numpy as np
          from paddle.fluid.dygraph.jit import declarative
179

180
          fluid.enable_dygraph()
181

182 183 184 185 186 187 188 189
          @declarative
          def func(x):
              x = fluid.dygraph.to_variable(x)
              if fluid.layers.mean(x) < 0:
                  x_v = x - 1
              else:
                  x_v = x + 1
              return x_v
190

191 192 193
          x = np.ones([1, 2])
          x_v = func(x)
          print(x_v.numpy()) # [[2. 2.]]
194

195
    """
196

197 198 199 200 201 202
    def decorated(python_func):
        """
        Decorates a python function into a StaticLayer object.
        """
        # Step 1. unwrap the function if it is already decorated.
        _, python_func = unwrap_decorators(python_func)
203

204 205 206 207 208 209 210
        # Step 2. copy some attributes from original python function.
        static_layer = copy_decorator_attrs(
            original_func=python_func,
            decorated_obj=StaticLayer(
                function=python_func, input_spec=input_spec))

        return static_layer
211

212 213 214
    # for usage: `declarative(foo, ...)`
    if function is not None:
        return decorated(function)
215

216 217
    # for usage: `@declarative`
    return decorated
218 219


220 221 222 223 224 225 226 227 228 229 230 231
class SaveLoadConfig(object):
    """
    The additional configuration options may be used in function 
    :ref:`api_imperative_jit_save` that save :ref:`api_imperative_TranslatedLayer` 
    or used in function :ref:`api_imperative_jit_load` that 
    load :ref:`api_imperative_TranslatedLayer` .
    
    Examples:
        1. Using ``SaveLoadConfig`` when saving model

        .. code-block:: python

232 233 234
            import paddle
            import paddle.nn as nn
            import paddle.optimizer as opt
235

236
            class SimpleNet(nn.Layer):
237 238
                def __init__(self, in_size, out_size):
                    super(SimpleNet, self).__init__()
239
                    self._linear = nn.Linear(in_size, out_size)
240

241
                @paddle.jit.to_static
242 243 244 245 246 247
                def forward(self, x):
                    y = self._linear(x)
                    z = self._linear(y)
                    return z

            # enable dygraph mode
248
            paddle.disable_static() 
249 250 251

            # train model
            net = SimpleNet(8, 8)
252 253
            adam = opt.Adam(learning_rate=0.1, parameters=net.parameters())
            x = paddle.randn([4, 8], 'float32')
254 255
            for i in range(10):
                out = net(x)
256
                loss = paddle.tensor.mean(out)
257
                loss.backward()
258 259
                adam.step()
                adam.clear_grad()
260 261 262

            # use SaveLoadconfig when saving model
            model_path = "simplenet.example.model"
263 264 265
            config = paddle.SaveLoadConfig()
            config.model_filename = "__simplenet__"
            paddle.jit.save(
266 267
                layer=net,
                model_path=model_path,
268
                config=config)
269 270 271 272 273

        2. Using ``SaveLoadConfig`` when loading model

        .. code-block:: python

274
            import paddle
275 276

            # enable dygraph mode
277
            paddle.disable_static() 
278 279 280

            # use SaveLoadconfig when loading model
            model_path = "simplenet.example.model"
281 282 283
            config = paddle.SaveLoadConfig()
            config.model_filename = "__simplenet__"
            infer_net = paddle.jit.load(model_path, config=config)
284
            # inference
285
            x = paddle.randn([4, 8], 'float32')
286 287 288 289 290 291 292 293
            pred = infer_net(x)
    """

    def __init__(self):
        self._output_spec = None
        self._model_filename = None
        self._params_filename = None
        self._separate_params = False
294 295
        # used for `paddle.load`
        self._keep_name_table = False
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

        # NOTE: Users rarely use following configs, so these configs are not open to users,
        # reducing user learning costs, but we retain the configuration capabilities

        # If True, programs are modified to only support direct inference deployment. 
        # Otherwise,more information will be stored for flexible optimization and re-training. 
        # Currently, only True is supported
        self._export_for_deployment = True

        # If True, It will save inference program only, and do not save params of Program
        self._program_only = False

    @property
    def output_spec(self):
        """
        Selects the output targets of the saved model ( :ref:`api_imperative_TranslatedLayer` ).
        By default, all return variables of original Layer's forward function
        are kept as the output of the saved TranslatedLayer.

        The ``output_spec`` type should be list[Variable]. If the provided ``output_spec``
        list is not all output variables, the saved model will be pruned according to the
        given ``output_spec`` list.

        .. note::
            The ``output_spec`` is only used when saving model.

        Examples:
            .. code-block:: python

325 326 327
                import paddle
                import paddle.nn as nn
                import paddle.optimizer as opt
328

329
                class SimpleNet(nn.Layer):
330 331
                    def __init__(self, in_size, out_size):
                        super(SimpleNet, self).__init__()
332
                        self._linear = nn.Linear(in_size, out_size)
333

334
                    @paddle.jit.to_static
335 336 337
                    def forward(self, x):
                        y = self._linear(x)
                        z = self._linear(y)
338
                        loss = paddle.tensor.mean(z)
339 340 341
                        return z, loss

                # enable dygraph mode
342
                paddle.disable_static() 
343 344 345

                # train model
                net = SimpleNet(8, 8)
346 347
                adam = opt.Adam(learning_rate=0.1, parameters=net.parameters())
                x = paddle.randn([4, 8], 'float32')
348 349 350
                for i in range(10):
                    out, loss = net(x)
                    loss.backward()
351 352
                    adam.step()
                    adam.clear_grad()
353 354 355

                # use SaveLoadconfig.output_spec
                model_path = "simplenet.example.model.output_spec"
356 357 358
                config = paddle.SaveLoadConfig()
                config.output_spec = [out]
                paddle.jit.save(
359 360
                    layer=net,
                    model_path=model_path,
361
                    config=config)
362

363 364
                infer_net = paddle.jit.load(model_path)
                x = paddle.randn([4, 8], 'float32')
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
                pred = infer_net(x)
        """
        return self._output_spec

    @output_spec.setter
    def output_spec(self, spec):
        if not isinstance(spec, list):
            raise TypeError(
                "The SaveLoadConfig.output_spec should be 'list', but received input type is %s."
                % type(input))
            for var in spec:
                if not isinstance(var, core.VarBase):
                    raise TypeError(
                        "The element in SaveLoadConfig.output_spec list should be 'Variable', but received element's type is %s."
                        % type(var))
        self._output_spec = spec

    @property
    def model_filename(self):
        """
        The name of file to save the translated program of target Layer.
        Default filename is :code:`__model__` .

388
        Examples:
389 390
            .. code-block:: python

391 392 393
                import paddle
                import paddle.nn as nn
                import paddle.optimizer as opt
394

395
                class SimpleNet(nn.Layer):
396 397
                    def __init__(self, in_size, out_size):
                        super(SimpleNet, self).__init__()
398
                        self._linear = nn.Linear(in_size, out_size)
399

400
                    @paddle.jit.to_static
401 402 403 404 405 406
                    def forward(self, x):
                        y = self._linear(x)
                        z = self._linear(y)
                        return z

                # enable dygraph mode
407
                paddle.disable_static() 
408 409 410

                # train model
                net = SimpleNet(8, 8)
411 412
                adam = opt.Adam(learning_rate=0.1, parameters=net.parameters())
                x = paddle.randn([4, 8], 'float32')
413 414
                for i in range(10):
                    out = net(x)
415
                    loss = paddle.tensor.mean(out)
416
                    loss.backward()
417 418
                    adam.step()
                    adam.clear_grad()
419 420

                # saving with configs.model_filename
421 422 423 424
                model_path = "simplenet.example.model.model_filename"
                config = paddle.SaveLoadConfig()
                config.model_filename = "__simplenet__"
                paddle.jit.save(
425 426
                    layer=net,
                    model_path=model_path,
427
                    config=config)
428 429

                # loading with configs.model_filename
430 431
                infer_net = paddle.jit.load(model_path, config=config)
                x = paddle.randn([4, 8], 'float32')
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
                pred = infer_net(x)
        """
        return self._model_filename

    @model_filename.setter
    def model_filename(self, filename):
        if not isinstance(filename, six.string_types):
            raise TypeError(
                "The SaveLoadConfig.model_filename should be str, but received input's type is %s."
                % type(filename))
        if len(filename) == 0:
            raise ValueError(
                "The SaveLoadConfig.model_filename is empty string.")
        self._model_filename = filename

    @property
    def params_filename(self):
        """
        The name of file to save all persistable variables in target Layer. 
        Default file name is :code:`__variables__` .
        
453
        Examples:
454 455
            .. code-block:: python

456 457 458
                import paddle
                import paddle.nn as nn
                import paddle.optimizer as opt
459

460
                class SimpleNet(nn.Layer):
461 462
                    def __init__(self, in_size, out_size):
                        super(SimpleNet, self).__init__()
463
                        self._linear = nn.Linear(in_size, out_size)
464

465
                    @paddle.jit.to_static
466 467 468 469 470 471
                    def forward(self, x):
                        y = self._linear(x)
                        z = self._linear(y)
                        return z

                # enable dygraph mode
472
                paddle.disable_static() 
473 474 475

                # train model
                net = SimpleNet(8, 8)
476 477
                adam = opt.Adam(learning_rate=0.1, parameters=net.parameters())
                x = paddle.randn([4, 8], 'float32')
478 479
                for i in range(10):
                    out = net(x)
480
                    loss = paddle.tensor.mean(out)
481
                    loss.backward()
482 483
                    adam.step()
                    adam.clear_grad()
484 485

                model_path = "simplenet.example.model.params_filename"
486 487
                config = paddle.SaveLoadConfig()
                config.params_filename = "__params__"
488 489

                # saving with configs.params_filename
490
                paddle.jit.save(
491 492
                    layer=net,
                    model_path=model_path,
493
                    config=config)
494 495

                # loading with configs.params_filename
496 497
                infer_net = paddle.jit.load(model_path, config=config)
                x = paddle.randn([4, 8], 'float32')
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530
                pred = infer_net(x)
        """
        return self._params_filename

    @params_filename.setter
    def params_filename(self, filename):
        if not isinstance(filename, six.string_types):
            raise TypeError(
                "The SaveLoadConfig.params_filename should be str, but received input's type is %s."
                % type(filename))
        if len(filename) == 0:
            raise ValueError(
                "The SaveLoadConfig.params_filename is empty string.")
        self._params_filename = filename

    # NOTE: [why not use params_filename=None control params saved separately]
    # The new save interface does not recommend parameters to be saved separately. 
    # Here, the concept should be separated as clearly as possible. 
    # Setting params_filename=None only means that the saved file name is set 
    # and without any other meaning. New separate_params control for file saved
    # separately can makes the concept clearer.
    @property
    def separate_params(self):
        """
        Configure whether to save the Layer parameters as separete files.
        (In order to be compatible with the behavior of :ref:`api_fluid_io_save_inference_model` )

        If True, each parameter will be saved to a file separately, the file name is the parameter name,
        and the SaveLoadConfig.params_filename configuration will not take effect. Default False.

        Examples:
            .. code-block:: python

531 532 533
                import paddle
                import paddle.nn as nn
                import paddle.optimizer as opt
534

535
                class SimpleNet(nn.Layer):
536 537
                    def __init__(self, in_size, out_size):
                        super(SimpleNet, self).__init__()
538
                        self._linear = nn.Linear(in_size, out_size)
539

540
                    @paddle.jit.to_static
541 542 543 544 545 546
                    def forward(self, x):
                        y = self._linear(x)
                        z = self._linear(y)
                        return z

                # enable dygraph mode
547
                paddle.disable_static() 
548 549 550

                # train model
                net = SimpleNet(8, 8)
551 552
                adam = opt.Adam(learning_rate=0.1, parameters=net.parameters())
                x = paddle.randn([4, 8], 'float32')
553 554
                for i in range(10):
                    out = net(x)
555
                    loss = paddle.tensor.mean(out)
556
                    loss.backward()
557 558
                    adam.step()
                    adam.clear_grad()
559 560

                model_path = "simplenet.example.model.separate_params"
561 562
                config = paddle.jit.SaveLoadConfig()
                config.separate_params = True
563 564

                # saving with configs.separate_params
565
                paddle.jit.save(
566 567
                    layer=net,
                    model_path=model_path,
568
                    config=config)
569 570 571 572
                # [result] the saved model directory contains:
                # linear_0.b_0  linear_0.w_0  __model__  __variables.info__

                # loading with configs.params_filename
573 574
                infer_net = paddle.jit.load(model_path, config=config)
                x = paddle.randn([4, 8], 'float32')
575 576 577 578 579 580 581 582 583 584 585 586
                pred = infer_net(x)
        """
        return self._separate_params

    @separate_params.setter
    def separate_params(self, value):
        if not isinstance(value, bool):
            raise TypeError(
                "The SaveLoadConfig.separate_params should be bool value, but received input's type is %s."
                % type(value))
        self._separate_params = value

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
    @property
    def keep_name_table(self):
        """
        Configures whether keep ``structured_name -> parameter_name`` dict in loaded state dict.
        This dict is the debugging information saved when call `paddle.save`. 
        It is generally only used for debugging and does not affect the actual training or inference. 
        By default, it will not be retained in `paddle.load` result. Default: False.
        
        .. note::
            Only used for ``paddle.load``.

        Examples:
            .. code-block:: python

                import paddle
            
                paddle.disable_static()

                linear = paddle.nn.Linear(5, 1)

                state_dict = linear.state_dict()
                paddle.save(state_dict, "paddle_dy")

                configs = paddle.SaveLoadConfig()
                configs.keep_name_table = True
                para_state_dict, _ = paddle.load("paddle_dy", configs)

                print(para_state_dict)
                # the name_table is 'StructuredToParameterName@@'
                # {'bias': array([0.], dtype=float32), 
                #  'StructuredToParameterName@@': 
                #     {'bias': u'linear_0.b_0', 'weight': u'linear_0.w_0'}, 
                #  'weight': array([[ 0.04230034],
                #     [-0.1222527 ],
                #     [ 0.7392676 ],
                #     [-0.8136974 ],
                #     [ 0.01211023]], dtype=float32)}
        """
        return self._keep_name_table

    @keep_name_table.setter
    def keep_name_table(self, value):
        if not isinstance(value, bool):
            raise TypeError(
                "The SaveLoadConfig.keep_name_table should be bool value, but received input's type is %s."
                % type(value))
        self._keep_name_table = value

635

636 637 638 639 640 641 642 643 644 645 646 647 648
# NOTE(chenweihang): change jit.save/load argument `configs` to `config`
def deprecate_save_load_configs(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        if 'configs' in kwargs:
            kwargs['config'] = kwargs['configs']
            kwargs.pop('configs')
        return func(*args, **kwargs)

    return wrapper


@deprecate_save_load_configs
649
@switch_to_static_graph
650
def save(layer, model_path, input_spec=None, config=None):
651 652 653 654 655 656 657 658 659 660
    """
    Saves input declarative Layer as :ref:`api_imperative_TranslatedLayer` 
    format model, which can be used for inference or fine-tuning after loading.

    It will save the translated program and all related persistable 
    variables of input declarative Layer to given ``model_path``.
    
    The default saved translated program file name is ``__model__``,
    and the default saved persistable variables file name is ``__variables__``,
    and it also saved some additional variable description information to file 
661
    ``__variables.info__``, these additional information is used in fine-tuning.
662 663 664 665 666 667 668 669 670

    The saved model can be loaded by follow APIs:
      - :ref:`api_imperative_jit_load`
      - :ref:`api_fluid_io_load_inference_model` (need pass ``params_filename='__variables__'``)
      - Other C++ inference APIs

    Args:
        layer (Layer): the Layer to be saved. The Layer should be decorated by `@declarative`.
        model_path (str): the directory to save the model.
671
        input_spec (list[Variable], optional): Describes the input of the saved model. 
672 673 674
            It is the example inputs that will be passed to saved TranslatedLayer's forward
            function. If None, all input variables of the original Layer's forward function
            would be the inputs of the saved model. Default None.
675
        config (SaveLoadConfig, optional): :ref:`api_imperative_jit_saveLoadConfig` object
676 677 678 679 680 681 682 683
            that specifies additional configuration options. Default None.
    Returns:
        None

    Examples:
        .. code-block:: python

            import numpy as np
684 685 686
            import paddle
            import paddle.nn as nn
            import paddle.optimizer as opt
687

688 689 690
            BATCH_SIZE = 16
            BATCH_NUM = 4
            EPOCH_NUM = 4
691

692 693 694 695 696 697 698
            IMAGE_SIZE = 784
            CLASS_NUM = 10

            # define a random dataset
            class RandomDataset(paddle.io.Dataset):
                def __init__(self, num_samples):
                    self.num_samples = num_samples
699

700 701 702 703
                def __getitem__(self, idx):
                    image = np.random.random([IMAGE_SIZE]).astype('float32')
                    label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
                    return image, label
704

705 706
                def __len__(self):
                    return self.num_samples
707

708 709
            class LinearNet(nn.Layer):
                def __init__(self):
710
                    super(LinearNet, self).__init__()
711
                    self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
712

713
                @paddle.jit.to_static
714 715 716
                def forward(self, x):
                    return self._linear(x)

717 718 719 720 721 722 723 724 725 726 727
            def train(layer, loader, loss_fn, opt):
                for epoch_id in range(EPOCH_NUM):
                    for batch_id, (image, label) in enumerate(loader()):
                        out = layer(image)
                        loss = loss_fn(out, label)
                        loss.backward()
                        opt.step()
                        opt.clear_grad()
                        print("Epoch {} batch {}: loss = {}".format(
                            epoch_id, batch_id, np.mean(loss.numpy())))

728
            # enable dygraph mode
729 730
            place = paddle.CPUPlace()
            paddle.disable_static(place) 
731

732
            # 1. train & save model.
733

734 735 736 737
            # create network
            layer = LinearNet()
            loss_fn = nn.CrossEntropyLoss()
            adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())
738

739 740 741 742 743 744 745 746
            # create data loader
            dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
            loader = paddle.io.DataLoader(dataset,
                places=place,
                batch_size=BATCH_SIZE,
                shuffle=True,
                drop_last=True,
                num_workers=2)
747

748 749
            # train
            train(layer, loader, loss_fn, adam)
750

751
            # save
752
            model_path = "linear.example.model"
753
            paddle.jit.save(layer, model_path)
754 755 756
    """

    def get_inout_spec(all_vars, target_vars, return_name=False):
757
        result_list = []
758
        valid_var_dict = {}
759
        valid_vars = [var for var in all_vars if isinstance(var, Variable)]
760 761 762 763 764 765 766 767
        for var in valid_vars:
            valid_var_dict[var.name] = var
        if target_vars:
            for i, var in enumerate(target_vars):
                # check target var whether exists
                if var.name not in valid_var_dict:
                    raise RuntimeError(
                        "The variable to feed/fetch are not exist.")
768
                result_list.append(valid_var_dict[var.name])
769
        else:
770
            result_list = valid_vars
771
        if return_name:
772
            result_list = [var.name for var in result_list]
773

774
        return result_list
775 776 777 778 779

    # 1. input check
    prog_translator = ProgramTranslator()
    if not prog_translator.enable:
        raise RuntimeError(
780
            "The paddle.jit.save doesn't work when setting ProgramTranslator.enable=False."
781 782 783
        )
    if not isinstance(layer, Layer):
        raise TypeError(
784
            "The input layer of paddle.jit.save should be 'Layer', but received layer type is %s."
785 786
            % type(layer))

787
    configs = config
788 789 790 791 792 793 794 795 796
    if configs is None:
        configs = SaveLoadConfig()

    if input_spec is not None:
        if not isinstance(input_spec, list):
            raise TypeError(
                "The input input_spec should be 'list', but received input_spec's type is %s."
                % type(input_spec))
        for var in input_spec:
797 798
            if not isinstance(var, (core.VarBase, Variable,
                                    paddle.static.InputSpec)):
799
                raise TypeError(
800
                    "The element in input_spec list should be 'Variable' or `paddle.static.InputSpec`, but received element's type is %s."
801 802 803
                    % type(var))

    # 2. get program of declarative Layer.forward
804 805 806 807
    if not isinstance(layer.forward, StaticLayer):
        raise RuntimeError(
            "layer.forward need to be decorated by `@declarative`.")
    concrete_program = layer.forward.concrete_program
808

809 810 811 812 813
    # NOTE: we maintain the mapping of variable name to
    # structured name, the buffer variable (non-persistable)
    # saved to inference program may not need by dygraph Layer, 
    # we only record the state_dict variable's structured name
    state_names_dict = dict()
814
    for structured_name, var in six.iteritems(layer.state_dict()):
815 816
        state_names_dict[var.name] = structured_name

817 818 819
    # 3. share parameters from Layer to scope & record var info
    scope = core.Scope()
    extra_var_info = dict()
820
    for param_or_buffer in concrete_program.parameters:
821 822 823 824 825 826
        # share to scope
        param_or_buffer_tensor = scope.var(param_or_buffer.name).get_tensor()
        src_tensor = param_or_buffer.value().get_tensor()
        param_or_buffer_tensor._share_data_with(src_tensor)
        # record var info
        extra_info_dict = dict()
827 828 829
        if param_or_buffer.name in state_names_dict:
            extra_info_dict['structured_name'] = state_names_dict[
                param_or_buffer.name]
830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878
        extra_info_dict['stop_gradient'] = param_or_buffer.stop_gradient
        if isinstance(param_or_buffer, ParamBase):
            extra_info_dict['trainable'] = param_or_buffer.trainable
        extra_var_info[param_or_buffer.name] = extra_info_dict

    # 4. build input & output spec
    input_var_names = get_inout_spec(concrete_program.inputs, input_spec, True)
    output_vars = get_inout_spec(concrete_program.outputs, configs.output_spec)

    # 5. save inference model
    from paddle.fluid.io import save_inference_model

    # VARIABLE_FILENAME keep nameing style consistent with '__model__'
    if configs.params_filename is None:
        configs.params_filename = VARIABLE_FILENAME

    with scope_guard(scope):
        save_inference_model(
            dirname=model_path,
            feeded_var_names=input_var_names,
            target_vars=output_vars,
            executor=Executor(_current_expected_place()),
            main_program=concrete_program.main_program.clone(),
            model_filename=configs.model_filename,
            params_filename=None
            if configs.separate_params else configs.params_filename,
            export_for_deployment=configs._export_for_deployment,
            program_only=configs._program_only)

        # NOTE: [ Save extra variable info ]
        # save_inference_model will lose some important variable information, including:
        #   - Variable name and correspondence (when saved variables as one file)
        #   - Variable.stop_gradient information
        #   - Which persistent variable are parameter and which are not
        #   - Parameter.trainable information
        #
        # The lost information cannot be recovered when it is loaded again, 
        # so if we want to perform fine-tune after loading, we may need to 
        # configure redundant information to proceed.
        #
        # Due to compatibility issues, we cannot change the original storage structure, 
        # but we can save these information in `jit.save` without changing the original 
        # storage to improve user experience. So we save extra information into
        # file `__variables.info__`
        extra_var_info_path = os.path.join(model_path, EXTRA_VAR_INFO_FILENAME)
        with open(extra_var_info_path, 'wb') as f:
            pickle.dump(extra_var_info, f, protocol=2)


879
@deprecate_save_load_configs
880
@dygraph_only
881
def load(model_path, config=None):
882 883 884 885 886 887 888 889 890 891
    """
    :api_attr: imperative

    Load model saved by :ref:`api_imperative_jit_save` or :ref:`api_fluid_io_save_inference_model`
    as :ref:`api_imperative_TranslatedLayer`, then performing inference or fine-tune training.

    .. note::
        For some historical reasons, if you load model saved by :ref:`api_fluid_io_save_inference_model`,
        there will be the following limitations when using it in fine-tuning:
        1. Imperative mode do not support LoDTensor. All original model's feed targets or parametars that depend on LoD are temporarily unavailable.
892
        2. All saved model's feed targets need to be passed into TranslatedLayer's forward function.
893 894 895 896 897
        3. The variable's ``stop_gradient`` information is lost and can not be recovered.
        4. The parameter's ``trainable`` information is lost and can not be recovered.

    Args:
        model_path (str): The directory path where the model is saved.
898
        config (SaveLoadConfig, optional): :ref:`api_imperative_jit_saveLoadConfig` object that specifies 
899 900 901 902 903 904 905 906 907 908 909
            additional configuration options. Default None.

    Returns:
        TranslatedLayer: A Layer object can run saved translated model.

    Examples:
        1. Load model saved by :ref:`api_imperative_jit_save` then performing inference and fine-tune training.

        .. code-block:: python

            import numpy as np
910 911 912
            import paddle
            import paddle.nn as nn
            import paddle.optimizer as opt
913

914 915 916
            BATCH_SIZE = 16
            BATCH_NUM = 4
            EPOCH_NUM = 4
917

918 919
            IMAGE_SIZE = 784
            CLASS_NUM = 10
920

921 922 923 924
            # define a random dataset
            class RandomDataset(paddle.io.Dataset):
                def __init__(self, num_samples):
                    self.num_samples = num_samples
925

926 927 928 929
                def __getitem__(self, idx):
                    image = np.random.random([IMAGE_SIZE]).astype('float32')
                    label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
                    return image, label
930

931 932 933 934 935
                def __len__(self):
                    return self.num_samples

            class LinearNet(nn.Layer):
                def __init__(self):
936
                    super(LinearNet, self).__init__()
937
                    self._linear = nn.Linear(IMAGE_SIZE, CLASS_NUM)
938

939
                @paddle.jit.to_static
940 941 942
                def forward(self, x):
                    return self._linear(x)

943 944 945 946 947 948 949 950 951 952 953
            def train(layer, loader, loss_fn, opt):
                for epoch_id in range(EPOCH_NUM):
                    for batch_id, (image, label) in enumerate(loader()):
                        out = layer(image)
                        loss = loss_fn(out, label)
                        loss.backward()
                        opt.step()
                        opt.clear_grad()
                        print("Epoch {} batch {}: loss = {}".format(
                            epoch_id, batch_id, np.mean(loss.numpy())))

954
            # enable dygraph mode
955 956
            place = paddle.CPUPlace()
            paddle.disable_static(place) 
957 958

            # 1. train & save model.
959

960
            # create network
961 962 963 964
            layer = LinearNet()
            loss_fn = nn.CrossEntropyLoss()
            adam = opt.Adam(learning_rate=0.001, parameters=layer.parameters())

965
            # create data loader
966 967 968 969 970 971 972
            dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
            loader = paddle.io.DataLoader(dataset,
                places=place,
                batch_size=BATCH_SIZE,
                shuffle=True,
                drop_last=True,
                num_workers=2)
973

974 975
            # train
            train(layer, loader, loss_fn, adam)
976

977 978 979
            # save
            model_path = "linear.example.model"
            paddle.jit.save(layer, model_path)
980

981
            # 2. load model
982

983 984
            # load
            loaded_layer = paddle.jit.load(model_path)
985 986

            # inference
987 988 989
            loaded_layer.eval()
            x = paddle.randn([1, IMAGE_SIZE], 'float32')
            pred = loaded_layer(x)
990 991

            # fine-tune
992 993 994
            loaded_layer.train()
            adam = opt.Adam(learning_rate=0.001, parameters=loaded_layer.parameters())
            train(loaded_layer, loader, loss_fn, adam)
995 996 997 998 999 1000 1001


        2. Load model saved by :ref:`api_fluid_io_save_inference_model` then performing and fine-tune training.

        .. code-block:: python

            import numpy as np
1002
            import paddle
1003
            import paddle.fluid as fluid
1004 1005
            import paddle.nn as nn
            import paddle.optimizer as opt
1006

1007 1008 1009
            BATCH_SIZE = 16
            BATCH_NUM = 4
            EPOCH_NUM = 4
1010

1011 1012 1013 1014 1015 1016 1017
            IMAGE_SIZE = 784
            CLASS_NUM = 10

            # define a random dataset
            class RandomDataset(paddle.io.Dataset):
                def __init__(self, num_samples):
                    self.num_samples = num_samples
1018

1019 1020 1021 1022
                def __getitem__(self, idx):
                    image = np.random.random([IMAGE_SIZE]).astype('float32')
                    label = np.random.randint(0, CLASS_NUM - 1, (1, )).astype('int64')
                    return image, label
1023

1024 1025
                def __len__(self):
                    return self.num_samples
1026

1027
            image = fluid.data(name='image', shape=[None, 784], dtype='float32')
1028
            label = fluid.data(name='label', shape=[None, 1], dtype='int64')
1029
            pred = fluid.layers.fc(input=image, size=10, act='softmax')
1030 1031 1032 1033 1034 1035 1036 1037 1038 1039
            loss = fluid.layers.cross_entropy(input=pred, label=label)
            avg_loss = fluid.layers.mean(loss)

            optimizer = fluid.optimizer.SGD(learning_rate=0.001)
            optimizer.minimize(avg_loss)

            place = fluid.CPUPlace()
            exe = fluid.Executor(place)
            exe.run(fluid.default_startup_program())

1040 1041 1042 1043 1044 1045 1046 1047 1048
            # create data loader
            dataset = RandomDataset(BATCH_NUM * BATCH_SIZE)
            loader = paddle.io.DataLoader(dataset,
                feed_list=[image, label],
                places=place,
                batch_size=BATCH_SIZE, 
                shuffle=True,
                drop_last=True,
                num_workers=2)
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058

            # 1. train and save inference model
            for data in loader():
                exe.run(
                    fluid.default_main_program(),
                    feed=data, 
                    fetch_list=[avg_loss])

            model_path = "fc.example.model"
            fluid.io.save_inference_model(
1059 1060 1061
                model_path, ["image"], [pred], exe)

            # 2. load model
1062 1063

            # enable dygraph mode
1064 1065 1066 1067
            paddle.disable_static(place)

            # load
            fc = paddle.jit.load(model_path)
1068

1069 1070 1071
            # inference
            fc.eval()
            x = paddle.randn([1, IMAGE_SIZE], 'float32')
1072 1073
            pred = fc(x)

1074
            # fine-tune
1075
            fc.train()
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
            loss_fn = nn.CrossEntropyLoss()
            adam = opt.Adam(learning_rate=0.001, parameters=fc.parameters())
            loader = paddle.io.DataLoader(dataset,
                places=place,
                batch_size=BATCH_SIZE,
                shuffle=True,
                drop_last=True,
                num_workers=2)
            for epoch_id in range(EPOCH_NUM):
                for batch_id, (image, label) in enumerate(loader()):
                    out = fc(image)
                    loss = loss_fn(out, label)
                    loss.backward()
                    adam.step()
                    adam.clear_grad()
                    print("Epoch {} batch {}: loss = {}".format(
                        epoch_id, batch_id, np.mean(loss.numpy())))
1093
    """
1094
    return TranslatedLayer._construct(model_path, config)
1095 1096


1097
@dygraph_only
Z
Zeng Jinle 已提交
1098 1099 1100 1101 1102
def _trace(layer,
           inputs,
           feed_prefix='feed_',
           fetch_prefix='fetch_',
           tmp_prefix='t_'):
1103
    assert isinstance(layer, Layer)
1104 1105 1106 1107 1108 1109 1110 1111 1112

    if not isinstance(inputs, (list, tuple)):
        inputs = [inputs]

    tracer = _dygraph_tracer()._get_program_desc_tracer()

    var_list = extract_vars(inputs)

    with program_desc_tracing_guard(True):
1113
        original_outputs = layer(*inputs)
1114 1115 1116 1117
        if not isinstance(original_outputs, (list, tuple)):
            outputs = [original_outputs]
        else:
            outputs = original_outputs
1118
        out_vars = [var for var in outputs]
1119

1120
        program_desc, feed_names, fetch_names, parameters = tracer.create_program_desc(
Z
Zeng Jinle 已提交
1121
            var_list, feed_prefix, out_vars, fetch_prefix, tmp_prefix)
1122 1123 1124 1125 1126
        tracer.reset()

    with _dygraph_guard(None):
        program = create_program_from_desc(program_desc)

1127
    return original_outputs, program, feed_names, fetch_names, parameters
1128 1129 1130 1131


class TracedLayer(object):
    """
1132 1133
    :api_attr: imperative
    
1134 1135 1136 1137 1138
    TracedLayer is used to convert a forward dygraph model to a static
    graph model. This is mainly used to save the dygraph model for online
    inference using C++. Besides, users can also do inference in Python
    using the converted static graph model, which usually has better
    performance than the original dygraph model.
1139 1140 1141 1142

    TracedLayer would run the static graph model using :code:`Executor`
    and :code:`CompiledProgram` . The static graph model would share
    parameters with the dygraph model.
1143 1144

    All TracedLayer objects should not be created by constructor and should
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155
    be created by static method :code:`TracedLayer.trace(layer, inputs)` .

    The TracedLayer can only be used to convert the data-independent dygraph
    model into the static graph model, which means the dygraph model should
    be independent with the tensor data and shape.
    """

    def __init__(self, program, parameters, feed_names, fetch_names):
        self._program = program
        self._feed_names = feed_names
        self._fetch_names = fetch_names
1156
        self._params = parameters
1157 1158 1159 1160 1161

        self._place = _current_expected_place()

        self._scope = core.Scope()
        for p in parameters:
1162
            src_tensor = p.value().get_tensor()
1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185
            dst_tensor = self._scope.var(p.name).get_tensor()
            dst_tensor._share_data_with(src_tensor)

        self._exe = Executor(self._place)
        self._compiled_program = None
        self._build_strategy = None
        self._exec_strategy = None

    @property
    def program(self):
        return self._program

    def _switch(self, is_test=True):
        for block_id in range(self._program.num_blocks):
            block = self._program.block(block_id)
            for op in block.ops:
                if op.has_attr("is_test"):
                    op._set_attr("is_test", is_test)

    @staticmethod
    @dygraph_only
    def trace(layer, inputs):
        """
1186
        This method is the only allowed method to create TracedLayer object.
1187 1188 1189 1190
        It would call the :code:`layer(*inputs)` method to run the dygraph
        model and convert it into a static graph model.

        Args:
1191
            layer (dygraph.Layer): the layer object to be traced.
1192 1193
            inputs (list(Tensor)|tuple(Tensor)|Tensor): the input tensors of
                the layer object.
1194 1195

        Returns:
1196
            tuple: A tuple of 2 items, whose the first item is the output of
1197 1198
                :code:`layer(*inputs)` , and the second item is the created
                TracedLayer object.
1199

1200
        Examples:
1201 1202 1203
            .. code-block:: python:

                import paddle.fluid as fluid
1204
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
1205 1206 1207
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
1208 1209 1210
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
1211 1212 1213 1214 1215

                    def forward(self, input):
                        return self._fc(input)

                with fluid.dygraph.guard():
1216
                    layer = ExampleLayer()
1217 1218 1219
                    in_np = np.random.random([2, 3]).astype('float32')
                    in_var = to_variable(in_np)
                    out_dygraph, static_layer = TracedLayer.trace(layer, inputs=[in_var])
1220 1221 1222 1223 1224 1225 1226 1227 1228

                    # run the static graph model using Executor inside
                    out_static_graph = static_layer([in_var])

                    print(len(out_static_graph)) # 1
                    print(out_static_graph[0].shape) # (2, 10)

                    # save the static graph model for inference
                    static_layer.save_inference_model(dirname='./saved_infer_model')
1229
        """
1230 1231 1232 1233
        assert isinstance(
            layer, Layer
        ), "The type of 'layer' in fluid.dygraph.jit.TracedLayer.trace must be fluid.dygraph.Layer, but received {}.".format(
            type(layer))
1234 1235
        outs, prog, feed, fetch, parameters = _trace(layer, inputs)
        traced = TracedLayer(prog, parameters, feed, fetch)
1236 1237 1238 1239 1240 1241 1242
        return outs, traced

    def set_strategy(self, build_strategy=None, exec_strategy=None):
        """
        Set the strategies when running static graph model.

        Args:
1243
            build_strategy (BuildStrategy, optional): build strategy of
1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
                :code:`CompiledProgram` inside TracedLayer. Default None.
            exec_strategy (ExecutionStrategy, optional): execution strategy of
                :code:`CompiledProgram` inside TracedLayer. Default None.

        Returns:
            None

        Examples:
            .. code-block:: python:

                import paddle.fluid as fluid
1255
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
1256 1257 1258
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
1259 1260 1261
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
1262 1263 1264 1265 1266

                    def forward(self, input):
                        return self._fc(input)

                with fluid.dygraph.guard():
1267
                    layer = ExampleLayer()
1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
                    in_np = np.random.random([2, 3]).astype('float32')
                    in_var = to_variable(in_np)

                    out_dygraph, static_layer = TracedLayer.trace(layer, inputs=[in_var])

                    build_strategy = fluid.BuildStrategy()
                    build_strategy.enable_inplace = True

                    exec_strategy = fluid.ExecutionStrategy()
                    exec_strategy.num_threads = 2

                    static_layer.set_strategy(build_strategy=build_strategy, exec_strategy=exec_strategy)
                    out_static_graph = static_layer([in_var])
        """
        assert self._compiled_program is None, "Cannot set strategy after run"
1283 1284 1285 1286 1287 1288 1289 1290
        assert isinstance(
            build_strategy, (type(None), BuildStrategy)
        ), "The type of 'build_strategy' in fluid.dygraph.jit.TracedLayer.set_strategy must be fluid.BuildStrategy, but received {}.".format(
            type(build_strategy))
        assert isinstance(
            exec_strategy, (type(None), ExecutionStrategy)
        ), "The type of 'exec_strategy' in fluid.dygraph.jit.TracedLayer.set_strategy must be fluid.ExecutionStrategy, but received {}.".format(
            type(exec_strategy))
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308
        self._build_strategy = build_strategy
        self._exec_strategy = exec_strategy

    @switch_to_static_graph
    def _compile(self):
        self._compiled_program = CompiledProgram(
            self._program).with_data_parallel(
                build_strategy=self._build_strategy,
                exec_strategy=self._exec_strategy,
                places=self._place)

    def _build_feed(self, inputs):
        assert isinstance(inputs, (list, tuple)), \
            "Inputs should be a list or tuple of variables"
        assert len(inputs) == len(self._feed_names)
        feed_dict = {}
        if in_dygraph_mode():
            for x, name in zip(inputs, self._feed_names):
1309
                feed_dict[name] = x.value().get_tensor()
1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331
        else:
            for x, name in zip(inputs, self._feed_names):
                feed_dict[name] = x

        return feed_dict

    @switch_to_static_graph
    def _run(self, feed):
        return self._exe.run(self._compiled_program,
                             feed=feed,
                             fetch_list=self._fetch_names)

    def __call__(self, inputs):
        with scope_guard(self._scope):
            if self._compiled_program is None:
                self._compile()

            return self._run(self._build_feed(inputs))

    @switch_to_static_graph
    def save_inference_model(self, dirname, feed=None, fetch=None):
        """
1332 1333
        Save the TracedLayer to a model for inference. The saved
        inference model can be loaded by C++ inference APIs.
1334 1335

        Args:
1336
            dirname (str): the directory to save the inference model.
1337
            feed (list[int], optional): the input variable indices of the saved
1338
                inference model. If None, all input variables of the
1339 1340 1341 1342 1343 1344 1345 1346
                TracedLayer object would be the inputs of the saved inference
                model. Default None.
            fetch (list[int], optional): the output variable indices of the
                saved inference model. If None, all output variables of the
                TracedLayer object would be the outputs of the saved inference
                model. Default None.

        Returns:
1347
            None
1348 1349 1350 1351 1352

        Examples:
            .. code-block:: python:

                import paddle.fluid as fluid
1353
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
1354 1355 1356
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
1357 1358 1359
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
1360 1361 1362 1363

                    def forward(self, input):
                        return self._fc(input)

1364 1365 1366
                save_dirname = './saved_infer_model'
                in_np = np.random.random([2, 3]).astype('float32')

1367
                with fluid.dygraph.guard():
1368
                    layer = ExampleLayer()
1369 1370
                    in_var = to_variable(in_np)
                    out_dygraph, static_layer = TracedLayer.trace(layer, inputs=[in_var])
1371
                    static_layer.save_inference_model(save_dirname, feed=[0], fetch=[0])
1372 1373

                place = fluid.CPUPlace()
1374 1375
                exe = fluid.Executor(place)
                program, feed_vars, fetch_vars = fluid.io.load_inference_model(save_dirname,
1376
                                                    exe)
1377 1378 1379

                fetch, = exe.run(program, feed={feed_vars[0]: in_np}, fetch_list=fetch_vars)
                print(fetch.shape) # (2, 10)
1380
        """
1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395
        check_type(dirname, "dirname", str,
                   "fluid.dygraph.jit.TracedLayer.save_inference_model")
        check_type(feed, "feed", (type(None), list),
                   "fluid.dygraph.jit.TracedLayer.save_inference_model")
        if isinstance(feed, list):
            for f in feed:
                check_type(f, "each element of feed", int,
                           "fluid.dygraph.jit.TracedLayer.save_inference_model")
        check_type(fetch, "fetch", (type(None), list),
                   "fluid.dygraph.jit.TracedLayer.save_inference_model")
        if isinstance(fetch, list):
            for f in fetch:
                check_type(f, "each element of fetch", int,
                           "fluid.dygraph.jit.TracedLayer.save_inference_model")

1396
        from paddle.fluid.io import save_inference_model
1397 1398 1399 1400 1401

        def get_feed_fetch(all_vars, partial_vars):
            if partial_vars is None:
                return all_vars

1402
            return [all_vars[idx] for idx in partial_vars]
1403 1404 1405 1406 1407 1408 1409 1410 1411 1412

        with scope_guard(self._scope):
            feeded_var_names = get_feed_fetch(self._feed_names, feed)
            target_var_names = get_feed_fetch(self._fetch_names, fetch)
            target_vars = []
            for name in target_var_names:
                target_var = self._program.global_block().vars.get(name, None)
                assert target_var is not None, "{} cannot be found".format(name)
                target_vars.append(target_var)

1413
            save_inference_model(
1414 1415 1416 1417 1418
                dirname=dirname,
                feeded_var_names=feeded_var_names,
                target_vars=target_vars,
                executor=self._exe,
                main_program=self._program.clone())