jit.py 16.2 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 warnings
20 21
from paddle.fluid import core
from paddle.fluid.compiler import CompiledProgram
22
from paddle.fluid.dygraph.base import program_desc_tracing_guard, switch_to_static_graph
23
from paddle.fluid.dygraph.dygraph_to_static.program_translator import ProgramTranslator
24 25 26 27
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
28 29 30 31 32 33 34 35 36 37 38 39


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

    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


53 54 55 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
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
102 103
    def __impl__(*args, **kwargs):
        program_translator = ProgramTranslator()
104
        if in_dygraph_mode() or not program_translator.enable_declarative:
105
            warnings.warn(
106
                "The decorator 'dygraph_to_static_func' doesn't work in "
107
                "dygraph mode or set ProgramTranslator.enable to False. "
108 109 110 111
                "We will just return dygraph output.")
            return dygraph_func(*args, **kwargs)
        static_func = program_translator.get_func(dygraph_func)
        return static_func(*args, **kwargs)
112 113 114 115

    return __impl__


116
dygraph_to_static_func = wrap_decorator(_dygraph_to_static_func_)
117

118

119 120 121 122 123
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.
124

125 126
    Args:
        dygraph_func (callable): callable imperative function.
127

128 129
    Returns:
        VarBase: containing the numerical result.
130

131 132
    Examples:
        .. code-block:: python
133

134 135 136
          import paddle.fluid as fluid
          import numpy as np
          from paddle.fluid.dygraph.jit import declarative
137

138

139 140 141 142 143 144 145 146
          @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
147

148 149 150
          x = np.ones([1, 2])
          x_v = func(x)
          print(x_v.numpy()) # [[2. 2.]]
151

152
    """
153 154

    def __impl__(*args, **kwargs):
155
        program_translator = ProgramTranslator()
156
        if not program_translator.enable_declarative:
157
            warnings.warn(
158 159
                "The decorator 'declarative' doesn't work when setting ProgramTranslator.enable=False. "
                "We will just return dygraph output.")
160
            return dygraph_func(*args, **kwargs)
161
        return program_translator.get_output(dygraph_func, *args, **kwargs)
162

163
    return __impl__
164 165


166
declarative = wrap_decorator(_declarative_)
167 168


169
@dygraph_only
Z
Zeng Jinle 已提交
170 171 172 173 174
def _trace(layer,
           inputs,
           feed_prefix='feed_',
           fetch_prefix='fetch_',
           tmp_prefix='t_'):
175
    assert isinstance(layer, Layer)
176 177 178 179 180 181 182 183 184

    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):
185
        original_outputs = layer(*inputs)
186 187 188 189
        if not isinstance(original_outputs, (list, tuple)):
            outputs = [original_outputs]
        else:
            outputs = original_outputs
190
        out_vars = [var for var in outputs]
191

192
        program_desc, feed_names, fetch_names, parameters = tracer.create_program_desc(
Z
Zeng Jinle 已提交
193
            var_list, feed_prefix, out_vars, fetch_prefix, tmp_prefix)
194 195 196 197 198
        tracer.reset()

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

199
    return original_outputs, program, feed_names, fetch_names, parameters
200 201 202 203


class TracedLayer(object):
    """
204 205
    :api_attr: imperative
    
206 207 208 209 210
    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.
211 212 213 214

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

    All TracedLayer objects should not be created by constructor and should
217 218 219 220 221 222 223 224 225 226 227
    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
228
        self._params = parameters
229 230 231 232 233

        self._place = _current_expected_place()

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

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

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

271
        Examples:
272 273 274
            .. code-block:: python:

                import paddle.fluid as fluid
275
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
276 277 278
                import numpy as np

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

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

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

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

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

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

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

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

                with fluid.dygraph.guard():
334
                    layer = ExampleLayer()
335 336 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
                    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):
368
                feed_dict[name] = x.value().get_tensor()
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390
        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):
        """
391 392
        Save the TracedLayer to a model for inference. The saved
        inference model can be loaded by C++ inference APIs.
393 394

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

        Examples:
            .. code-block:: python:

                import paddle.fluid as fluid
412
                from paddle.fluid.dygraph import Linear, to_variable, TracedLayer
413 414 415
                import numpy as np

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

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

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

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

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

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

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

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

        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)

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