-
Notifications
You must be signed in to change notification settings - Fork 6k
Update save inference model to support dygraph #25894
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 45 commits
463359e
4709a25
b595db4
db663c4
b7850ed
314e3e2
2ffcddc
196df6a
6caa61b
c625a50
8f6cb8f
c7cc35f
753aef9
86adb71
43bd7d2
08688f5
c61da24
0939851
25efe73
cca0c0a
92ab5d9
87aaf3c
19f5ec1
d45488e
4f6fda6
7d3bf0f
743f249
103f8bf
5777091
9d1be86
5fef294
0ee2da7
f569de7
5cd1d8a
1453d03
a4e44af
fa11228
ead375a
d01442d
eeb08bb
4f1223d
aa96150
869bc19
26fcde3
2d29fac
04b2d5a
3777dc7
3e6f384
9dffc17
1e4aea5
92d280c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| # Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved. | ||
| # Copyright (c) 2020 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. | ||
|
|
@@ -25,17 +25,21 @@ | |
| from collections import Iterable | ||
|
|
||
| from paddle import fluid | ||
| from paddle.fluid import core | ||
| from paddle.fluid.framework import in_dygraph_mode, Variable, ParamBase, _current_expected_place | ||
| # Note: Use alias `Input` temporarily before releasing hapi feature. | ||
| from paddle.static import InputSpec as Input | ||
| from paddle.fluid.framework import in_dygraph_mode, Variable | ||
| from paddle.fluid.executor import global_scope | ||
| from paddle.fluid.io import is_belong_to_optimizer | ||
| from paddle.fluid.dygraph.base import to_variable | ||
| from paddle.fluid.dygraph.parallel import ParallelEnv | ||
| from paddle.fluid.dygraph.dygraph_to_static.program_translator import ProgramTranslator, FunctionSpec | ||
| from paddle.fluid.layers.utils import flatten | ||
| from paddle.fluid.incubate.fleet.collective import fleet, DistributedStrategy | ||
| from paddle.fluid.incubate.fleet.base import role_maker | ||
| from paddle.fluid.executor import scope_guard, Executor | ||
| from paddle.io import DataLoader, Dataset | ||
| from paddle.fluid.dygraph.layers import Layer | ||
|
|
||
| from .distributed import DistributedBatchSampler, _all_gather, prepare_distributed_context, _parallel_context_initialized | ||
| from .metrics import Metric | ||
|
|
@@ -849,50 +853,80 @@ def forward(self, x): | |
| """ | ||
| return self._adapter.test_batch(inputs) | ||
|
|
||
| def save(self, path): | ||
| """ | ||
| This function saves parameters, optimizer infomation to path. | ||
| def save(self, path, for_inference=False): | ||
| """ | ||
| This function saves parameters, optimizer information or model and | ||
| paramters only for inference to path. It depends on the parameter | ||
| `for_inference`. | ||
|
|
||
| The parameters contains all the trainable Variable, will save to | ||
| a file with suffix ".pdparams". | ||
| If `for_inference` is set to Fasle, the parameters saved contain all | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. False拼写错误
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, thanks! |
||
| the trainable Variable, will save to a file with suffix ".pdparams". | ||
| The optimizer information contains all the variable used by optimizer. | ||
| For Adam optimizer, contains beta1, beta2, momentum etc. All the | ||
| information will save to a file with suffix ".pdopt". (If the optimizer | ||
| have no variable need to save (like SGD), the fill will not generated). | ||
| This function will silently overwrite existing file at the target location. | ||
|
|
||
| This function will silently overwrite existing file | ||
| at the target location. | ||
| If `for_inference` is set to True, only inference model will be saved. It | ||
| should be noted that before using `save`, you should run the model, and | ||
| the shape of input you saved is as same as the input of its running. | ||
| `@paddle.jit.to_static` must be added on `forward` function of your layer | ||
| in dynamic mode now and these will be optimized later. | ||
|
|
||
| Args: | ||
| path (str): The file prefix to save model. The format is | ||
| 'dirname/file_prefix' or 'file_prefix'. if empty str. A exception | ||
| will be raised. | ||
| for_inference (bool): Whether to save inference model only. Default: False. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 是一个可选参数,有默认值
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. Thanks! |
||
|
|
||
| Returns: | ||
| None | ||
|
|
||
| Examples: | ||
|
|
||
| .. code-block:: python | ||
| import paddle | ||
|
|
||
| import paddle.fluid as fluid | ||
| import paddle.incubate.hapi as hapi | ||
|
|
||
| class MyNet(fluid.dygraph.Layer): | ||
| def __init__(self): | ||
| super(MyNet, self).__init__() | ||
| self._fc = fluid.dygraph.Linear(784, 1, act='softmax') | ||
| import paddle.incubate.hapi as hapi | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 后续需要按照我们的新目录更新下 |
||
| from paddle.nn import Linear | ||
| from paddle.incubate.hapi.datasets.mnist import MNIST as MnistDataset | ||
|
|
||
| class Mnist(paddle.nn.Layer): | ||
| def __init__(self): | ||
| super(MyNet, self).__init__() | ||
| self._fc = Linear(784, 1, act='softmax') | ||
|
|
||
| @paddle.jit.to_static # If save for inference in dygraph, need this | ||
| def forward(self, x): | ||
| y = self._fc(x) | ||
| return y | ||
|
|
||
| device = hapi.set_device('cpu') | ||
| fluid.enable_dygraph(device) | ||
| model = hapi.Model(MyNet()) | ||
| model.save('checkpoint/test') | ||
|
|
||
| dynamic = True # False | ||
| device = hapi.set_device('cpu') | ||
| # if use static graph, do not set | ||
| paddle.disable_static(device) if dynamic else None | ||
|
|
||
| # inputs and labels are not required for dynamic graph. | ||
| input = hapi.Input([None, 784], 'float32', 'x') | ||
| label = hapi.Input([None, 1], 'int64', 'label') | ||
|
|
||
| model = hapi.Model(Mnist(), input, label) | ||
| optim = paddle.optimizer.SGD(learning_rate=1e-3, | ||
| parameter_list=model.parameters()) | ||
| model.prepare(optim, | ||
| paddle.nn.CrossEntropyLoss(), | ||
| hapi.metrics.Accuracy()) | ||
| mnist_data = hapi.datasets.MNIST(mode='train', chw_format=False) | ||
| model.fit(mnist_data, epochs=1, batch_size=32, verbose=0) | ||
| model.save('checkpoint/test') # save checkpoint | ||
| model.save('inference_model', True) # save for inference | ||
| """ | ||
|
|
||
| if ParallelEnv().local_rank == 0: | ||
| self._adapter.save(path) | ||
| if for_inference: | ||
| self._save_inference_model(path) | ||
| else: | ||
| self._adapter.save(path) | ||
|
|
||
| def load(self, path, skip_mismatch=False, reset_optimizer=False): | ||
| """ | ||
|
|
@@ -1480,13 +1514,17 @@ def __len__(self): | |
| cbks.on_end('test', logs) | ||
| return outputs | ||
|
|
||
| def save_inference_model(self, | ||
| save_dir, | ||
| model_filename=None, | ||
| params_filename=None, | ||
| model_only=False): | ||
| def _save_inference_model(self, | ||
| save_dir, | ||
| model_filename=None, | ||
| params_filename=None, | ||
| model_only=False): | ||
| """ | ||
| Save inference model must in static mode. | ||
| Save inference model can be in static or dynamic mode. | ||
| It should be noted that before using `save_inference_model`, you should | ||
| run the model, and the shape you saved is as same as the input of its | ||
| running. `@paddle.jit.to_static` must be added on `forward` function of | ||
| your layer in dynamic mode now and these will be optimized later. | ||
|
|
||
| Args: | ||
| save_dir (str): The directory path to save the inference model. | ||
|
|
@@ -1502,40 +1540,145 @@ def save_inference_model(self, | |
| Returns: | ||
| list: The fetch variables' name list | ||
|
|
||
|
|
||
| Examples: | ||
| .. code-block:: python | ||
|
|
||
| import paddle.fluid as fluid | ||
| import numpy as np | ||
| import paddle | ||
| from paddle.static import InputSpec | ||
| import paddle.incubate.hapi as hapi | ||
|
|
||
| input = hapi.Input([-1, 1, 28, 28], 'float32', 'image') | ||
| model = hapi.Model(hapi.vision.LeNet(), input) | ||
| model.prepare() | ||
|
|
||
| from paddle.nn import Linear | ||
| from paddle.incubate.hapi.datasets.mnist import MNIST as MnistDataset | ||
|
|
||
| class Mnist(Layer): | ||
| def __init__(self, classifier_act=None): | ||
| super(Mnist, self).__init__() | ||
|
|
||
| self.fc = Linear(input_dim=784, output_dim=10, act="softmax") | ||
|
|
||
| @paddle.jit.to_static # In static mode, you need to delete this. | ||
| def forward(self, inputs): | ||
| outputs = self.fc(inputs) | ||
| return outputs | ||
|
|
||
| dynamic = True # False | ||
| device = hapi.set_device('gpu') | ||
|
|
||
| # if use static graph, do not set | ||
| paddle.disable_static(device) if dynamic else None | ||
|
|
||
| # inputs and labels are not required for dynamic graph. | ||
| input = InputSpec('x', [None, 784], 'float32') | ||
| label = InputSpec('label', [None, 1], 'int64') | ||
|
|
||
| model = hapi.Model(Mnist(), input, label) | ||
| optim = paddle.optimizer.SGD(learning_rate=1e-3, | ||
| parameter_list=model.parameters()) | ||
| model.prepare(optim, | ||
| paddle.nn.CrossEntropyLoss(), | ||
| hapi.metrics.Accuracy()) | ||
| mnist_data = hapi.datasets.MNIST(mode='train', chw_format=False) | ||
| model.fit(mnist_data, epochs=1, batch_size=32, verbose=0) | ||
| model.save_inference_model('inference_model') | ||
| """ | ||
| assert not fluid.in_dygraph_mode( | ||
| ), 'Save inference model must in static mode!' | ||
|
|
||
| prog = self._adapter._progs.get('test', None) | ||
| assert prog, \ | ||
| "Model is not ready, please call `model.prepare()` first" | ||
| def get_inout_spec(all_vars, return_name=False): | ||
| result_list = [] | ||
| valid_vars = [var for var in all_vars if isinstance(var, Variable)] | ||
| result_list = valid_vars | ||
| if return_name: | ||
| result_list = [var.name for var in result_list] | ||
|
|
||
| infer_prog = prog.clone(for_test=True) | ||
| return result_list | ||
|
|
||
| input_names = [v.name for v in self._adapter._input_vars['test']] | ||
| endpoints = self._adapter._endpoints['test']['output'] | ||
| # TODO: | ||
| # 1. Make it Unnecessary to run model before calling `save_inference_model` for users in dygraph. | ||
| # 2. Save correct shape of input, now the interface stores the shape that the user sent to | ||
| # the inputs of the model in running. | ||
| # 3. Make it Unnecessary to add `@paddle.jit.to_static` for users in dynamic mode. | ||
| if fluid.in_dygraph_mode(): | ||
|
LiuChiachi marked this conversation as resolved.
|
||
| layer = self.network | ||
| fluid.disable_dygraph() | ||
|
|
||
| # 1. input check | ||
| prog_translator = ProgramTranslator() | ||
| if not prog_translator.enable_declarative: | ||
| raise RuntimeError( | ||
| "save_inference_model doesn't work when setting ProgramTranslator.enable=False." | ||
| ) | ||
| if not isinstance(layer, Layer): | ||
| raise TypeError( | ||
| "The input layer should be 'Layer', but received layer type is %s." | ||
| % type(layer)) | ||
|
|
||
| # 2. get program of declarative Layer.forward | ||
| prog_cache = prog_translator.get_program_cache() | ||
| # make dummy args & kwargs, to get excepted FunctionSpec | ||
| layer_func = FunctionSpec(type(layer).forward, [layer], {}) | ||
| concrete_program, _ = prog_cache.get_program(layer_func) | ||
|
|
||
| # 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() | ||
| for structured_name, var in layer.state_dict().items(): | ||
| state_names_dict[var.name] = structured_name | ||
|
|
||
| # 3. share parameters from Layer to scope & record var info | ||
| scope = core.Scope() | ||
| extra_var_info = dict() | ||
| for param_or_buffer in concrete_program.parameters: | ||
| # 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() | ||
| if param_or_buffer.name in state_names_dict: | ||
| extra_info_dict['structured_name'] = state_names_dict[ | ||
| param_or_buffer.name] | ||
| 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, True) | ||
| output_vars = get_inout_spec(concrete_program.outputs) | ||
|
LiuChiachi marked this conversation as resolved.
|
||
|
|
||
| # 5. save inference model | ||
| with scope_guard(scope): | ||
| return fluid.io.save_inference_model( | ||
| dirname=save_dir, | ||
| feeded_var_names=input_var_names, | ||
| target_vars=output_vars, | ||
| executor=Executor(_current_expected_place()), | ||
| main_program=concrete_program.main_program.clone(), | ||
| model_filename=model_filename, | ||
| params_filename=params_filename, | ||
| program_only=model_only) | ||
|
|
||
| return fluid.io.save_inference_model( | ||
| save_dir, | ||
| input_names, | ||
| endpoints, | ||
| self._adapter._executor, | ||
| main_program=infer_prog, | ||
| model_filename=model_filename, | ||
| params_filename=params_filename, | ||
| program_only=model_only) | ||
| else: | ||
| prog = self._adapter._progs.get('test', None) | ||
| assert prog, \ | ||
| "Model is not ready, please call `model.prepare()` first" | ||
|
|
||
| infer_prog = prog.clone(for_test=True) | ||
|
|
||
| input_names = [v.name for v in self._adapter._input_vars['test']] | ||
| endpoints = self._adapter._endpoints['test']['output'] | ||
|
|
||
| return fluid.io.save_inference_model( | ||
| save_dir, | ||
| input_names, | ||
| endpoints, | ||
| self._adapter._executor, | ||
| main_program=infer_prog, | ||
| model_filename=model_filename, | ||
| params_filename=params_filename, | ||
| program_only=model_only) | ||
|
|
||
| def _run_one_epoch(self, data_loader, callbacks, mode, logs={}): | ||
| outputs = [] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for_inference=False -> training=True
建议跟组网类的API的参数保持一致,用training这个参数标识训练和预测