Created by: Aurelius84
PR types
New features
PR changes
APIs
Describe
- 动转静
@declarative
升级为返回callable的类对象 - 新增
InputSpec
概念,支持设置input_spec
进行动转静
What's New?
declarative
返回callable类对象
1. 1.1 背景介绍
此前版本的@declarative
装饰函数后,仅返回callable的另一个函数。其接收传递过来的*args,**kwargs
参数,执行内部cache的program,返回结果。基本逻辑如下:
def declarative(func):
def __impl__(*args, **kwargs):
out = program_translator.get_output(*args, **kwargs)
return out
return __impl__
此方案可以满足大部分场景,但具有一定的局限性:
-
@declarative
无法支持其他参数,接口功能扩展性较差 - 返回
__impl__
函数而非类对象,不能友好地暴露属性访问的相关接口 - 需要搭配
ProgramTranslator
进行使用,增加了熟悉框架新概念的成本
因此,此PR升级了declarative
接口,以类对象作为返回。
1.2 新功能使用
- 保持使用方式不变
# 方式一:
@declarative
def foo(x, y):
return x + y
# 方式二:
foo = declarative(foo)
z = foo(x_var, y_var)
- 直接通过
foo
访问属性
# (接上面)
# 1. function相关信息
foo.dygraph_function # 返回 被装饰函数
foo.to_code() # 返回 转静态图后的代码
foo._function_spec # 返回 被装饰函数相关的FunctionSpec对象
foo._function_spec.code # 返回 被装饰函数的代码
foo._function_spec.args_name # 返回 被装饰函数的参数列表
# 2. Program信息
foo.program_cache # 返回 管理被装饰函数转写结果的ProgramCache对象
foo.program_cache.concrete_programs() # 返回 被装饰函数对应的所有cache的program列表
foo.get_trace_count() # 返回 已转写的Program的数量
foo.concrete_program # 返回 最新转写的ConcreteProgram对象
- 与ProgramTranslator解耦 如上示例,获取被装饰Function的code、program信息,均不依赖ProgramTranslator。 接口使用方式更加简洁、易用。且每个函数单独持有一份ProgramCache,避免交叉。
out = foo(x_var, y_var) # 返回 执行结果
InputSpec
2. 新增支持2.1 背景介绍
此前@declarative
装饰器不支持任何额外的的参数。若要获取转换后的Program
,则需要显式的执行一次前向:
# 方式一:
out = foo(fake_x, fake_y)
program_translator.get_program_cache().last()
# 方式二:
program_translator.get_program(foo, fake_x, fake_y)
此方案具有如下局限,或不易用之处:
- 强依赖显式地fake数据,接口不易用
- 无法指定input tensor的某些维度为None,无法指定feed layer的name
- 不支持被装饰函数
编译式
转换
此PR新增了InputSpec
类,并重构了@declarative
逻辑,支持input_spec
参数来指定feed layer的shape、name信息。
2.2 InputSpec类
InputSpec
类似于C++端的VarDesc
概念,用于表示一个Tensor的元信息:shape
、dtype
、name
。用户可以通过指定被装饰函数输入参数对应的InputSpec
信息,来进行后续Program的推导。
- 支持从Tensor和numpy中推导
np_var = np.zeros([2, 3]).astype('float32')
var_spec = InputSpec.from_numpy(np_var, name='x')
# 或者
var = to_variable(np_var)
var_spec = InputSpec.from_variable(var)
- 支持
batch
和unbatch
操作
batch_var_spec = var_spec.batch(64) # shape= [64, 2, 3]
unbatch_var_spec = var_spec.unbatch() # shape= [3]
input_spec
2.3 @declarative支持重构@declarative
逻辑,支持input_spec
参数,同时也支持后续的横向功能扩展
-
@declarative
移除了无法指定额外参数的限制,可根据功能扩展参数。 - 支持通过
input_spec
指定Tensor shape为None
# 方式一:
@declarative(input_spec=[InputSpec([None, 10], dtype='float32'), InputSpec([10], name='y')])
def foo(x, y):
return x + y
# 方式二:
foo = declarative(foo, input_spec=[InputSpec([None, 10], dtype='float32'), InputSpec([10], name='y')])
- 支持
编译式
静态转换,不依赖fake_input
# x.shape = [None, 10], y.shape=[10]
concrete_program_1 = foo.get_concrete_program(InputSpec([None, 10]), InputSpec([10]))
# build new program with x.shape = [10], y.shape = [10]
concrete_program_2 = foo.get_concrete_program(InputSpec([10]), InputSpec([10]))
- 对于
非Tensor
类型参数的友好支持
# 如果被装饰函数参数中,包含非Tensor类型参数,也支持hashKey计算,得到不同的program
# 由于每个非Tensor值的改变都会触发新Program的构建,框架内部会trace记录已缓存的program
# 超过MAX_TRACE_COUNT=5,则会warning提示用户。
concrete_program_3 = foo.get_concrete_program(InputSpec([None, 10]), InputSpec([10]), c=2)
- 友好支持
嵌套类型
的input
# case 1: 输入`l`为一个list
@declarative(input_spec=[[InputSpec([None, 10]), InputSpec([None, 10])]])
def func_with_list(self, l):
x, y, int_val = l
z = x + y
z = z + int_val
return z
# case2: 输入`d` 为一个dict
@declarative(input_spec=[{
'x': InputSpec([None, 10]),
'y': InputSpec([None, 10])
}])
def func_with_dict(self, d):
x = d['x']
y = d['y']
int_val = d['int_val']
z = x + y
z = z + int_val
return z
# case 3: 输入为list嵌套dict
@declarative(input_spec=[[
InputSpec([None]), {
'x': InputSpec([None, 10]),
'y': InputSpec([None, 10])
}
]])
def func_with_list_dict(self, dl):
bias = dl[0]
x = dl[1]['x']
y = dl[1]['y']
z = x + y
z = z + bias
return z
- 支持对同一个class的不同函数进行单独装饰,分开存储
class SimpleNet(Layer):
def __init__(self):
super(SimpleNet, self).__init__()
self.linear = fluid.dygraph.Linear(10, 3)
@declarative(input_spec=[InputSpec(shape=[None, 10], dtype='float32')])
def forward(self, x, a=1, b=2):
y = self.inner_function(x)
return y
# `declarative` is not essential, add it to test for robustness.
@declarative
def inner_function(self, x):
y = self.linear(x)
return y
def add_func(self, x, y):
z = x + y
return z
# net.forward已在类定义中装饰
# 这里可以单独对net.add_func单独装饰,单独保存
net.add_func = declarative(net.add_func)
TODO
- 优化InputSpec与
jit.save
接口的搭配使用,提升易用性