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 logging
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
logger = logging.getLogger("fluid")

31 32 33 34 35 36 37 38 39 40 41

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

    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


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

    return __impl__


118
dygraph_to_static_func = wrap_decorator(_dygraph_to_static_func_)
119

120

121 122 123 124 125
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.
126

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

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

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

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

140

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

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

154
    """
155 156

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

165
    return __impl__
166 167


168
declarative = wrap_decorator(_declarative_)
169 170


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

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

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

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

201
    return original_outputs, program, feed_names, fetch_names, parameters
202 203 204 205


class TracedLayer(object):
    """
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())