未验证 提交 41d22472 编写于 作者: C Chen Weihang 提交者: GitHub

[Dy2static] Refactor ProgramTranslator save_inference_model API (#24989)

* experimental refactoring, test=develop

* add TranslatedLayer & remove StaticModelRunner, test=develop

* revert tracedlayer change, test=develop

* fix test_mnist unittest error, test=develop

* add doc & examples, test=develop

* polish doc details, test=develop

* add imperative.jit module, test=develop

* change TranslatedLayer pos, test=develop

* adjust jit module import path, test=develop

* polish doc based review result

* add SaveLoadConfig.separate_params to save paraams separately

* add Layer.buffer support, test=develop

* polish doc details based review result, test=develop

* polish details baesd review comments, test=develop

* add empty str check for param, test=develop

* add unittests, test=develop

* polish details based review comment, test=develop

* remove blanks in comment, test=develop

* polish doc details, test=develop

* update imperative doc link, test=develop

* add api attr for load, test=develop
上级 43f9f180
...@@ -49,13 +49,13 @@ static void CheckInputVarStatus(const Variable &var, ...@@ -49,13 +49,13 @@ static void CheckInputVarStatus(const Variable &var,
var.IsType<LoDTensor>(), true, var.IsType<LoDTensor>(), true,
platform::errors::InvalidArgument( platform::errors::InvalidArgument(
"The input variable %s of " "The input variable %s of "
"RunProgram(Grad)Op(StaticModelRunner) holds " "RunProgram(Grad)Op holds "
"wrong type. Expect type is LoDTensor, but receive type is %s.", "wrong type. Expect type is LoDTensor, but receive type is %s.",
var_name, platform::demangle(framework::ToTypeName(var.Type())))); var_name, platform::demangle(framework::ToTypeName(var.Type()))));
PADDLE_ENFORCE_EQ( PADDLE_ENFORCE_EQ(
var.Get<LoDTensor>().IsInitialized(), true, var.Get<LoDTensor>().IsInitialized(), true,
platform::errors::InvalidArgument("The tensor in input variable %s of " platform::errors::InvalidArgument("The tensor in input variable %s of "
"RunProgram(Grad)Op(StaticModelRunner) " "RunProgram(Grad)Op "
"is not initialized.", "is not initialized.",
var_name)); var_name));
} }
...@@ -68,14 +68,14 @@ static void CheckOutputVarStatus(const Variable &src_var, ...@@ -68,14 +68,14 @@ static void CheckOutputVarStatus(const Variable &src_var,
src_var.IsType<LoDTensor>(), true, src_var.IsType<LoDTensor>(), true,
platform::errors::InvalidArgument( platform::errors::InvalidArgument(
"The output variable %s get from " "The output variable %s get from "
"RunProgram(Grad)Op(StaticModelRunner)'s internal scope holds " "RunProgram(Grad)Op's internal scope holds "
"wrong type. Expect type is LoDTensor, but receive type is %s.", "wrong type. Expect type is LoDTensor, but receive type is %s.",
var_name, var_name,
platform::demangle(framework::ToTypeName(src_var.Type())))); platform::demangle(framework::ToTypeName(src_var.Type()))));
PADDLE_ENFORCE_EQ(src_var.Get<LoDTensor>().IsInitialized(), true, PADDLE_ENFORCE_EQ(src_var.Get<LoDTensor>().IsInitialized(), true,
platform::errors::InvalidArgument( platform::errors::InvalidArgument(
"The tensor in output variable %s get from " "The tensor in output variable %s get from "
"RunProgram(Grad)Op(StaticModelRunner)'s internal " "RunProgram(Grad)Op's internal "
"scope is not initialized.", "scope is not initialized.",
var_name)); var_name));
} else if (dst_var.IsType<SelectedRows>()) { } else if (dst_var.IsType<SelectedRows>()) {
...@@ -83,20 +83,20 @@ static void CheckOutputVarStatus(const Variable &src_var, ...@@ -83,20 +83,20 @@ static void CheckOutputVarStatus(const Variable &src_var,
src_var.IsType<SelectedRows>(), true, src_var.IsType<SelectedRows>(), true,
platform::errors::InvalidArgument( platform::errors::InvalidArgument(
"The output variable %s get from " "The output variable %s get from "
"RunProgram(Grad)Op(StaticModelRunner)'s internal scope holds " "RunProgram(Grad)Op's internal scope holds "
"wrong type. Expect type is SelectedRows, but receive type is %s.", "wrong type. Expect type is SelectedRows, but receive type is %s.",
var_name, var_name,
platform::demangle(framework::ToTypeName(src_var.Type())))); platform::demangle(framework::ToTypeName(src_var.Type()))));
PADDLE_ENFORCE_EQ(src_var.Get<SelectedRows>().value().IsInitialized(), true, PADDLE_ENFORCE_EQ(src_var.Get<SelectedRows>().value().IsInitialized(), true,
platform::errors::InvalidArgument( platform::errors::InvalidArgument(
"The tensor in output variable %s get from " "The tensor in output variable %s get from "
"RunProgram(Grad)Op(StaticModelRunner)'s " "RunProgram(Grad)Op's "
"internal scope is not initialized.", "internal scope is not initialized.",
var_name)); var_name));
} else { } else {
PADDLE_THROW(platform::errors::InvalidArgument( PADDLE_THROW(platform::errors::InvalidArgument(
"The RunProgram(Grad)Op(StaticModelRunner) only support output " "The RunProgram(Grad)Op only support output "
"variable of type LoDTensor or SelectedRows, " "variable of type LoDTensor or SelectedRows, "
"but received variable %s's type is %s", "but received variable %s's type is %s",
var_name, platform::demangle(framework::ToTypeName(dst_var.Type())))); var_name, platform::demangle(framework::ToTypeName(dst_var.Type()))));
...@@ -143,7 +143,7 @@ static void ShareVarsFromScope(const std::vector<Variable *> &vars, ...@@ -143,7 +143,7 @@ static void ShareVarsFromScope(const std::vector<Variable *> &vars,
auto *var = scope->FindVar(var_names[i]); auto *var = scope->FindVar(var_names[i]);
PADDLE_ENFORCE_NOT_NULL( PADDLE_ENFORCE_NOT_NULL(
var, platform::errors::NotFound("The output variable %s is not in " var, platform::errors::NotFound("The output variable %s is not in "
"RunProgram(Grad)Op(StaticModelRunner)'" "RunProgram(Grad)Op'"
"s internal scope.", "s internal scope.",
var_names[i])); var_names[i]));
CheckOutputVarStatus(*var, *vars[i], var_names[i]); CheckOutputVarStatus(*var, *vars[i], var_names[i]);
......
...@@ -44,6 +44,9 @@ from .backward_strategy import * ...@@ -44,6 +44,9 @@ from .backward_strategy import *
from . import jit from . import jit
from .jit import * from .jit import *
from . import io
from .io import *
from . import static_runner from . import static_runner
from .static_runner import StaticModelRunner from .static_runner import StaticModelRunner
...@@ -63,5 +66,6 @@ __all__ += checkpoint.__all__ ...@@ -63,5 +66,6 @@ __all__ += checkpoint.__all__
__all__ += learning_rate_scheduler.__all__ __all__ += learning_rate_scheduler.__all__
__all__ += backward_strategy.__all__ __all__ += backward_strategy.__all__
__all__ += jit.__all__ __all__ += jit.__all__
__all__ += io.__all__
__all__ += rnn.__all__ __all__ += rnn.__all__
__all__ += ['ProgramTranslator'] __all__ += ['ProgramTranslator']
...@@ -36,6 +36,7 @@ from paddle.fluid.wrapped_decorator import signature_safe_contextmanager ...@@ -36,6 +36,7 @@ from paddle.fluid.wrapped_decorator import signature_safe_contextmanager
from paddle.fluid.dygraph.base import param_guard from paddle.fluid.dygraph.base import param_guard
from paddle.fluid.data_feeder import check_type from paddle.fluid.data_feeder import check_type
from paddle.fluid.dygraph.dygraph_to_static.partial_program import partial_program_from from paddle.fluid.dygraph.dygraph_to_static.partial_program import partial_program_from
from paddle.fluid.annotations import deprecated
__all__ = ['ProgramTranslator', 'convert_to_static'] __all__ = ['ProgramTranslator', 'convert_to_static']
...@@ -125,6 +126,9 @@ class FunctionSpec(object): ...@@ -125,6 +126,9 @@ class FunctionSpec(object):
self._args = args self._args = args
self._kwargs = kwargs self._kwargs = kwargs
dyfunc = getattr(func, '__wrapped__', func)
self._dyfunc_code = inspect.getsource(dyfunc)
def is_method(self): def is_method(self):
return self._args and isinstance(self._args[0], layers.Layer) return self._args and isinstance(self._args[0], layers.Layer)
...@@ -198,7 +202,9 @@ class FunctionSpec(object): ...@@ -198,7 +202,9 @@ class FunctionSpec(object):
# Note: if dygraph function is a method of class, # Note: if dygraph function is a method of class,
# consider instance info as hash key. # consider instance info as hash key.
if self.is_method(): if self.is_method():
return self._dyfunc, self._args[0] # NOTE: we can use Layer's (instance + function code) as hash key.
# An instance will not hold two identical methods
return self._dyfunc_code, self._args[0]
else: else:
return self._dyfunc return self._dyfunc
...@@ -312,6 +318,17 @@ class ProgramCache(object): ...@@ -312,6 +318,17 @@ class ProgramCache(object):
self._caches[item] = self._build_once(item) self._caches[item] = self._build_once(item)
return self._caches[item] return self._caches[item]
def get_program(self, item):
if not isinstance(item, FunctionSpec):
raise ValueError(
"Input item's type should be FunctionSpec, but received %s" %
type(item))
if item not in self._caches:
raise RuntimeError(
"Failed to find program for input item, please decorate input function by `@declarative`."
)
return self._caches[item]
def last(self): def last(self):
assert len( assert len(
self._caches) >= 1, "No valid cached program in ProgramCache." self._caches) >= 1, "No valid cached program in ProgramCache."
...@@ -633,6 +650,7 @@ class ProgramTranslator(object): ...@@ -633,6 +650,7 @@ class ProgramTranslator(object):
source_code = ast_to_source_code(root_wrapper.node) source_code = ast_to_source_code(root_wrapper.node)
return source_code return source_code
@deprecated(since='2.0', instead="paddle.imperative.jit.save")
@switch_to_static_graph @switch_to_static_graph
def save_inference_model(self, dirname, feed=None, fetch=None): def save_inference_model(self, dirname, feed=None, fetch=None):
""" """
......
此差异已折叠。
此差异已折叠。
...@@ -26,6 +26,7 @@ from paddle.fluid.dygraph import to_variable ...@@ -26,6 +26,7 @@ from paddle.fluid.dygraph import to_variable
from paddle.fluid.dygraph.nn import Conv2D, Linear, Pool2D from paddle.fluid.dygraph.nn import Conv2D, Linear, Pool2D
from paddle.fluid.optimizer import AdamOptimizer from paddle.fluid.optimizer import AdamOptimizer
from paddle.fluid.dygraph.jit import declarative from paddle.fluid.dygraph.jit import declarative
from paddle.fluid.dygraph.io import VARIABLE_FILENAME
from paddle.fluid.dygraph.dygraph_to_static import ProgramTranslator from paddle.fluid.dygraph.dygraph_to_static import ProgramTranslator
SEED = 2020 SEED = 2020
...@@ -201,6 +202,9 @@ class TestMNISTWithDeclarative(TestMNIST): ...@@ -201,6 +202,9 @@ class TestMNISTWithDeclarative(TestMNIST):
self.check_save_inference_model([dy_x_data, y_data], self.check_save_inference_model([dy_x_data, y_data],
prog_trans, to_static, prog_trans, to_static,
prediction) prediction)
# new save load check
self.check_jit_save_load(mnist, [dy_x_data], [img],
to_static, prediction)
break break
return loss_data return loss_data
...@@ -224,6 +228,45 @@ class TestMNISTWithDeclarative(TestMNIST): ...@@ -224,6 +228,45 @@ class TestMNISTWithDeclarative(TestMNIST):
return np.array(results[0]) return np.array(results[0])
def check_jit_save_load(self, model, inputs, input_spec, to_static, gt_out):
if to_static:
infer_model_path = "./test_mnist_inference_model_by_jit_save"
configs = fluid.dygraph.jit.SaveLoadConfig()
configs.output_spec = [gt_out]
fluid.dygraph.jit.save(
layer=model,
model_path=infer_model_path,
input_spec=input_spec,
configs=configs)
# load in static mode
static_infer_out = self.jit_load_and_run_inference_static(
infer_model_path, inputs)
self.assertTrue(np.allclose(gt_out.numpy(), static_infer_out))
# load in dygraph mode
dygraph_infer_out = self.jit_load_and_run_inference_dygraph(
infer_model_path, inputs)
self.assertTrue(np.allclose(gt_out.numpy(), dygraph_infer_out))
@switch_to_static_graph
def jit_load_and_run_inference_static(self, model_path, inputs):
exe = fluid.Executor(self.place)
[inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(
dirname=model_path,
executor=exe,
params_filename=VARIABLE_FILENAME)
assert len(inputs) == len(feed_target_names)
results = exe.run(inference_program,
feed=dict(zip(feed_target_names, inputs)),
fetch_list=fetch_targets)
return np.array(results[0])
def jit_load_and_run_inference_dygraph(self, model_path, inputs):
infer_net = fluid.dygraph.jit.load(model_path)
pred = infer_net(inputs[0])
return pred.numpy()
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
# 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.
# 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.
from __future__ import print_function
import unittest
import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph import Linear
from paddle.fluid.dygraph import declarative
BATCH_SIZE = 32
BATCH_NUM = 20
SEED = 10
def random_batch_reader():
def _get_random_images_and_labels(image_shape, label_shape):
np.random.seed(SEED)
image = np.random.random(size=image_shape).astype('float32')
label = np.random.random(size=label_shape).astype('int64')
return image, label
def __reader__():
for _ in range(BATCH_NUM):
batch_image, batch_label = _get_random_images_and_labels(
[BATCH_SIZE, 784], [BATCH_SIZE, 1])
yield batch_image, batch_label
return __reader__
class LinearNet(fluid.dygraph.Layer):
def __init__(self, in_size, out_size):
super(LinearNet, self).__init__()
self._linear = Linear(in_size, out_size)
@declarative
def forward(self, x):
return self._linear(x)
class LinearNetNotDeclarative(fluid.dygraph.Layer):
def __init__(self, in_size, out_size):
super(LinearNetNotDeclarative, self).__init__()
self._linear = Linear(in_size, out_size)
def forward(self, x):
return self._linear(x)
class LinearNetReturnLoss(fluid.dygraph.Layer):
def __init__(self, in_size, out_size):
super(LinearNetReturnLoss, self).__init__()
self._linear = Linear(in_size, out_size)
@declarative
def forward(self, x):
y = self._linear(x)
z = self._linear(y)
loss = fluid.layers.mean(z)
return z, loss
def train(layer):
# create optimizer
adam = fluid.optimizer.AdamOptimizer(
learning_rate=0.1, parameter_list=layer.parameters())
# create data loader
train_loader = fluid.io.DataLoader.from_generator(capacity=5)
train_loader.set_batch_generator(random_batch_reader())
# train
for data in train_loader():
img, label = data
label.stop_gradient = True
cost = layer(img)
loss = fluid.layers.cross_entropy(cost, label)
avg_loss = fluid.layers.mean(loss)
avg_loss.backward()
adam.minimize(avg_loss)
layer.clear_gradients()
return [img], layer, avg_loss
def infer(layer):
x = fluid.dygraph.to_variable(np.random.random((1, 784)).astype('float32'))
return layer(x)
class TestJitSaveLoad(unittest.TestCase):
def setUp(self):
self.model_path = "model.test_jit_save_load"
# enable dygraph mode
fluid.enable_dygraph()
# config seed
fluid.default_main_program().random_seed = SEED
def train_and_save_model(self):
layer = LinearNet(784, 1)
example_inputs, layer, _ = train(layer)
fluid.dygraph.jit.save(
layer=layer, model_path=self.model_path, input_spec=example_inputs)
return layer
def test_save(self):
# train and save model
self.train_and_save_model()
def test_load_infernece(self):
# train and save model
train_layer = self.train_and_save_model()
# load model
infer_layer = fluid.dygraph.jit.load(self.model_path)
train_layer.eval()
# inference & compare
x = fluid.dygraph.to_variable(
np.random.random((1, 784)).astype('float32'))
self.assertTrue(
np.array_equal(train_layer(x).numpy(), infer_layer(x).numpy()))
def test_load_finetune(self):
# train and save model
train_layer = self.train_and_save_model()
# load model
load_train_layer = fluid.dygraph.jit.load(self.model_path)
load_train_layer.train()
# train & compare
_, _, train_loss = train(train_layer)
_, _, load_train_loss = train(load_train_layer)
self.assertTrue(
np.array_equal(train_loss.numpy(), load_train_loss.numpy()))
def test_save_get_program_failed(self):
layer = LinearNetNotDeclarative(784, 1)
example_inputs, layer, _ = train(layer)
with self.assertRaises(RuntimeError):
fluid.dygraph.jit.save(
layer=layer,
model_path=self.model_path,
input_spec=example_inputs)
class TestJitSaveLoadConfig(unittest.TestCase):
def setUp(self):
# enable dygraph mode
fluid.enable_dygraph()
# config seed
fluid.default_main_program().random_seed = SEED
def basic_save_load(self, layer, model_path, configs):
# 1. train & save
example_inputs, train_layer, _ = train(layer)
fluid.dygraph.jit.save(
layer=train_layer,
model_path=model_path,
input_spec=example_inputs,
configs=configs)
# 2. load
infer_layer = fluid.dygraph.jit.load(model_path, configs=configs)
train_layer.eval()
# 3. inference & compare
x = fluid.dygraph.to_variable(
np.random.random((1, 784)).astype('float32'))
self.assertTrue(
np.array_equal(train_layer(x).numpy(), infer_layer(x).numpy()))
def test_model_filename(self):
layer = LinearNet(784, 1)
model_path = "model.save_load_config.output_spec"
configs = fluid.dygraph.jit.SaveLoadConfig()
configs.model_filename = "__simplenet__"
self.basic_save_load(layer, model_path, configs)
def test_params_filename(self):
layer = LinearNet(784, 1)
model_path = "model.save_load_config.params_filename"
configs = fluid.dygraph.jit.SaveLoadConfig()
configs.params_filename = "__params__"
self.basic_save_load(layer, model_path, configs)
def test_separate_params(self):
layer = LinearNet(784, 1)
model_path = "model.save_load_config.separate_params"
configs = fluid.dygraph.jit.SaveLoadConfig()
configs.separate_params = True
self.basic_save_load(layer, model_path, configs)
def test_output_spec(self):
train_layer = LinearNetReturnLoss(8, 8)
adam = fluid.optimizer.AdamOptimizer(
learning_rate=0.1, parameter_list=train_layer.parameters())
x = fluid.dygraph.to_variable(
np.random.random((4, 8)).astype('float32'))
for i in range(10):
out, loss = train_layer(x)
loss.backward()
adam.minimize(loss)
train_layer.clear_gradients()
model_path = "model.save_load_config.output_spec"
configs = fluid.dygraph.jit.SaveLoadConfig()
configs.output_spec = [out]
fluid.dygraph.jit.save(
layer=train_layer,
model_path=model_path,
input_spec=[x],
configs=configs)
train_layer.eval()
infer_layer = fluid.dygraph.jit.load(model_path, configs=configs)
x = fluid.dygraph.to_variable(
np.random.random((4, 8)).astype('float32'))
self.assertTrue(
np.array_equal(train_layer(x)[0].numpy(), infer_layer(x).numpy()))
if __name__ == '__main__':
unittest.main()
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
__all__ = [ __all__ = [
'BackwardStrategy', 'enabled', 'grad', 'guard', 'LayerList', 'load', 'save', 'BackwardStrategy', 'enabled', 'grad', 'guard', 'LayerList', 'load', 'save',
'prepare_context', 'to_variable', 'TracedLayer', 'no_grad', 'ParallelEnv', 'prepare_context', 'to_variable', 'TracedLayer', 'no_grad', 'ParallelEnv',
'ProgramTranslator', 'declarative', 'DataParallel' 'ProgramTranslator', 'declarative', 'DataParallel', 'TranslatedLayer', 'jit'
] ]
__all__ += [ __all__ += [
...@@ -31,6 +31,7 @@ from ..fluid.dygraph.checkpoint import save_dygraph as save ...@@ -31,6 +31,7 @@ from ..fluid.dygraph.checkpoint import save_dygraph as save
from ..fluid.dygraph.parallel import prepare_context, ParallelEnv, DataParallel from ..fluid.dygraph.parallel import prepare_context, ParallelEnv, DataParallel
from ..fluid.dygraph.jit import TracedLayer, declarative from ..fluid.dygraph.jit import TracedLayer, declarative
from ..fluid.dygraph import ProgramTranslator from ..fluid.dygraph import ProgramTranslator
from . import jit
from ..fluid.dygraph.learning_rate_scheduler import NoamDecay, PiecewiseDecay, NaturalExpDecay, ExponentialDecay, \ from ..fluid.dygraph.learning_rate_scheduler import NoamDecay, PiecewiseDecay, NaturalExpDecay, ExponentialDecay, \
InverseTimeDecay, PolynomialDecay, CosineDecay InverseTimeDecay, PolynomialDecay, CosineDecay
......
# 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.
# 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.
from ...fluid.dygraph.jit import save, load, SaveLoadConfig
from ...fluid.dygraph.io import TranslatedLayer
__all__ = ['save', 'load', 'SaveLoadConfig']
...@@ -202,6 +202,7 @@ packages=['paddle', ...@@ -202,6 +202,7 @@ packages=['paddle',
'paddle.nn.initializer', 'paddle.nn.initializer',
'paddle.metric', 'paddle.metric',
'paddle.imperative', 'paddle.imperative',
'paddle.imperative.jit',
'paddle.tensor', 'paddle.tensor',
] ]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册