jit.py 16.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
__all__ = ['TracedLayer', 'declarative', 'dygraph_to_static_func']
18

19
import logging
20

21 22
from paddle.fluid import core
from paddle.fluid.compiler import CompiledProgram
23
from paddle.fluid.dygraph.base import program_desc_tracing_guard, switch_to_static_graph
24
from paddle.fluid.dygraph.dygraph_to_static.program_translator import ProgramTranslator
25 26 27 28
from paddle.fluid.dygraph.layers import Layer
from paddle.fluid.executor import Executor, scope_guard
from paddle.fluid.framework import Program, Block, Variable, _dygraph_tracer, dygraph_only, _dygraph_guard, _current_expected_place, in_dygraph_mode
from paddle.fluid.wrapped_decorator import wrap_decorator
29

30 31
logger = logging.getLogger("fluid")

32 33 34 35 36 37 38 39 40 41 42

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):
43
        result_list.append(inputs)
44 45 46 47 48 49 50 51 52 53 54 55

    if isinstance(inputs, (list, tuple)):
        for var in inputs:
            _extract_vars(var, result_list)


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


56 57 58 59 60 61 62 63 64 65 66 67 68 69 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
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
105 106
    def __impl__(*args, **kwargs):
        program_translator = ProgramTranslator()
107 108 109
        if in_dygraph_mode() or not program_translator.enable_declarative:
            logger.info(
                "The decorator 'dygraph_to_static_func' doesn't work in "
110
                "dygraph mode or set ProgramTranslator.enable to False. "
111 112 113 114
                "We will just return dygraph output.")
            return dygraph_func(*args, **kwargs)
        static_func = program_translator.get_func(dygraph_func)
        return static_func(*args, **kwargs)
115 116 117 118

    return __impl__


119
dygraph_to_static_func = wrap_decorator(_dygraph_to_static_func_)
120

121

122 123 124 125 126
def _declarative_(dygraph_func):
    """
    Converts imperative dygraph APIs into declarative function APIs. Decorator
    @declarative handles the Program and Executor of static mode and returns
    the result as a dygraph VarBase.
127

128 129
    Args:
        dygraph_func (callable): callable imperative function.
130

131 132
    Returns:
        VarBase: containing the numerical result.
133

134 135
    Examples:
        .. code-block:: python
136

137 138 139
          import paddle.fluid as fluid
          import numpy as np
          from paddle.fluid.dygraph.jit import declarative
140

141

142 143 144 145 146 147 148 149
          @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
150

151 152 153
          x = np.ones([1, 2])
          x_v = func(x)
          print(x_v.numpy()) # [[2. 2.]]
154

155
    """
156 157

    def __impl__(*args, **kwargs):
158 159 160 161
        program_translator = ProgramTranslator()
        if in_dygraph_mode() or not program_translator.enable_declarative:
            logger.info(
                "The decorator 'declarative' doesn't work in dygraph "
162
                "mode or set ProgramTranslator.enable to False. We will "
163
                "just return dygraph output.")
164
            return dygraph_func(*args, **kwargs)
165 166
        program_translator = ProgramTranslator()
        return program_translator.get_output(dygraph_func, *args, **kwargs)
167

168
    return __impl__
169 170


171
declarative = wrap_decorator(_declarative_)
172 173


174
@dygraph_only
Z
Zeng Jinle 已提交
175 176 177 178 179
def _trace(layer,
           inputs,
           feed_prefix='feed_',
           fetch_prefix='fetch_',
           tmp_prefix='t_'):
180
    assert isinstance(layer, Layer)
181 182 183 184 185 186 187 188 189

    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):
190
        original_outputs = layer(*inputs)
191 192 193 194
        if not isinstance(original_outputs, (list, tuple)):
            outputs = [original_outputs]
        else:
            outputs = original_outputs
195
        out_vars = [var for var in outputs]
196

197
        program_desc, feed_names, fetch_names, parameters = tracer.create_program_desc(
Z
Zeng Jinle 已提交
198
            var_list, feed_prefix, out_vars, fetch_prefix, tmp_prefix)
199 200 201 202 203
        tracer.reset()

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

204
    return original_outputs, program, feed_names, fetch_names, parameters
205 206 207 208


class TracedLayer(object):
    """
209 210 211 212 213
    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.
214 215 216 217

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

    All TracedLayer objects should not be created by constructor and should
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
    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

        self._place = _current_expected_place()

        self._scope = core.Scope()
        for p in parameters:
236
            src_tensor = p.value().get_tensor()
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
            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):
        """
260
        This method is the only allowed method to create TracedLayer object.
261 262 263 264
        It would call the :code:`layer(*inputs)` method to run the dygraph
        model and convert it into a static graph model.

        Args:
265 266
            layer (dygraph.Layer): the layer object to be traced.
            inputs (list(Variable)): the input variables of the layer object.
267 268

        Returns:
269
            tuple: A tuple of 2 items, whose the first item is the output of
270
            :code:`layer(*inputs)` , and the second item is the created
271
            TracedLayer object.
272

273
        Examples:
274 275 276
            .. code-block:: python:

                import paddle.fluid as fluid
277
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
278 279 280
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
281 282 283
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
284 285 286 287 288

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

                with fluid.dygraph.guard():
289
                    layer = ExampleLayer()
290 291 292
                    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])
293 294 295 296 297 298 299 300 301

                    # 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')
302
        """
303 304
        outs, prog, feed, fetch, parameters = _trace(layer, inputs)
        traced = TracedLayer(prog, parameters, feed, fetch)
305 306 307 308 309 310 311
        return outs, traced

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

        Args:
312
            build_strategy (BuildStrategy, optional): build strategy of
313 314 315 316 317 318 319 320 321 322 323
                :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
324
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
325 326 327
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
328 329 330
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
331 332 333 334 335

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

                with fluid.dygraph.guard():
336
                    layer = ExampleLayer()
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
                    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"
        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):
370
                feed_dict[name] = x.value().get_tensor()
371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
        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):
        """
393 394
        Save the TracedLayer to a model for inference. The saved
        inference model can be loaded by C++ inference APIs.
395 396

        Args:
397
            dirname (str): the directory to save the inference model.
398
            feed (list[int], optional): the input variable indices of the saved
399
                inference model. If None, all input variables of the
400 401 402 403 404 405 406 407
                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:
408
            None
409 410 411 412 413

        Examples:
            .. code-block:: python:

                import paddle.fluid as fluid
414
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
415 416 417
                import numpy as np

                class ExampleLayer(fluid.dygraph.Layer):
418 419 420
                    def __init__(self):
                        super(ExampleLayer, self).__init__()
                        self._fc = Linear(3, 10)
421 422 423 424

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

425 426 427
                save_dirname = './saved_infer_model'
                in_np = np.random.random([2, 3]).astype('float32')

428
                with fluid.dygraph.guard():
429
                    layer = ExampleLayer()
430 431
                    in_var = to_variable(in_np)
                    out_dygraph, static_layer = TracedLayer.trace(layer, inputs=[in_var])
432
                    static_layer.save_inference_model(save_dirname, feed=[0], fetch=[0])
433 434

                place = fluid.CPUPlace()
435 436
                exe = fluid.Executor(place)
                program, feed_vars, fetch_vars = fluid.io.load_inference_model(save_dirname,
437
                                                    exe)
438 439 440

                fetch, = exe.run(program, feed={feed_vars[0]: in_np}, fetch_list=fetch_vars)
                print(fetch.shape) # (2, 10)
441
        """
442
        from paddle.fluid.io import save_inference_model
443 444 445 446 447

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

448
            return [all_vars[idx] for idx in partial_vars]
449 450 451 452 453 454 455 456 457 458

        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)

459
            save_inference_model(
460 461 462 463 464
                dirname=dirname,
                feeded_var_names=feeded_var_names,
                target_vars=target_vars,
                executor=self._exe,
                main_program=self._program.clone())