支持语法¶
一、主要针对场景¶
本文档概览性介绍了飞桨动转静功能的语法支持情况,旨在提供一个便捷的语法速查表,主要适用于如下场景:
不确定当前动态图模型是否可以正确转化为静态图
转化过程中出现了问题但不知道如何排查
当出现不支持的语法时,如何修改源码适配动转静语法
若初次接触动转静功能,或对此功能尚不熟悉,推荐阅读:使用样例;
若动静转换遇到了问题,或想学习调试的技巧,推荐阅读:报错调试。
二、语法支持速查列表¶
分类 | python 语法 | 是否 支持 |
概要 |
---|---|---|---|
控制流 | if-else | 支持 | 自适应识别和转为静态图 cond 接口,或保持 python if 执行 |
while | 支持 | 自适应识别和转为静态图 while_loop 接口,或保持 python while 执行 | |
for | 支持 | for _ in x 语法支持对 Tensor 的迭代访问 |
|
break continue |
支持 | 支持循环中任意位置的 break 和 continue | |
return | 支持 | 支持循环体中提前 return | |
运算符 | +,-,,/,*, >, <, >= , <=, == | 支持 | 自适应识别和应用 paddle 的运算符重载 |
and, or, not | 支持 | 1.如果运算符两个都是 Tensor,会组网静态图。 2. 如果运算符都不是 Tensor,那么使用原始 python 语义 3. 如果一个是 Tensor 一个是非 Tensor,那么也会使用 python 语义,但是结果不会出错。 |
|
类型转换运算符 | 支持 | 自适应转换为 paddle.cast 操作 | |
Paddle shape | Tensor.shape() | 部分支持 | 支持获取编译期 shape 信息,可能包含-1 |
python 函数类 | print(x) | 支持 | 自适应识别和转为静态图的 PrintOp |
len(x) | 支持 | 支持返回 Tensor 编译期 shape[0]的值 | |
lambda 表达式 | 支持 | 等价转换 | |
函数调用其他函数 | 支持 | 会对内部的函数递归地进行动转静 | |
函数递归调用 | 不支持 | 递归调用不会终止 | |
list sort | 不支持 | list 可能会被转化为 TensorArray,故不支持此复杂操作 | |
报错异常相关 | assert | 支持 | 自适应识别和转换为静态图 Assert 接口 |
Python 基本容器 | list | 部分支持 | 在控制流中转化为 TensorArray,支持 append,pop |
Dict | 支持 | 原生支持 | |
第三方库相关 | numpy | 部分支持 | 仅支持 numpy 操作不需要导出到 Program |
三、详细说明¶
3.1 if-else¶
主要逻辑: 在动态图中,模型代码是一行一行解释执行的,因此控制流的条件变量是在运行期确定的,意味着 False 的逻辑分支不会被执行。
在静态图中,控制流通过cond
接口实现。每个分支分别通过true_fn
和false_fn
来表示。
当 if
中的条件
是Tensor
时,动转静会自动把该if-elif-else
语句转化为静态图的cond
API 语句。
当if
中的条件
不是Tensor
时,会按普通 Python if-else 的逻辑运行。
注:当
条件
为Tensor
时,只接受numel()==1
的 bool Tensor,否则会报错。
错误修改指南:
当模型代码中的if-else
转换或执行报错时,可以参考如下方式排查:
使用
if
语句时,请确定条件变量
是否是Paddle.Tensor
类型。若不是 Tensor 类型,则会当按照常规的 python 逻辑执行,而不会转化为静态图。若
if
中条件变量
为 Tensor 类型,需确保其为 boolean 类型,且tensor.numel()
为 1。
3.2 while 循环¶
主要逻辑:
当 while
循环中的条件是 Tensor 时,动转静会把该 while 语句转化为静态图中的while_loop
API 语句,否则会按普通 Python while 运行。
注:while 循环条件中的 Tensor 须是 numel 为 1 的 bool Tensor,否则会报错。
错误修改指南:
类似 if-elif-else
,注意事项也相似。
3.3 for 循环¶
主要逻辑:
for 循环按照使用方法的不同,语义有所不同。正常而言,for 循环的使用分为如下种类:
for _ in range(len)
循环:动转静会先将其转化为等价的 Python while 循环,然后按 while 循环的逻辑进行动静转换。for _ in x
循环: 当 x 是 Python 容器或迭代器,则会用普通 Python 逻辑运行。当 x 是 Tensor 时,会转化为依次获取 x[0], x[1], ... 。for idx, val in enumerate(x)
循环:当 x 是 Python 容器或迭代器,则会用普通 Python 逻辑运行。当 x 是 Tensor 时,idx 会转化为依次 0,1,...的 1-D Tensor。val 会转化为循环中每次对应拿出 x[0], x[1], ... 。
从实现而言,for 循环最终会转化为对应的 while 语句,然后使用WhileOp
来进行组网。
使用样例:
此处使用上述 For 的第二个用法举例。如果 x 是一个多维 Tensor,则也是返回 x[0] ,x[1]. ...
def ForTensor(x):
"""Fetch element in x and print the square of each x element"""
for i in x :
print (i * i)
#调用方法,ForTensor(paddle.to_tensor(x))
3.4 流程控制语句说明 (return / break / continue)¶
主要逻辑:
目前的动转静支持 for、while 等循环中添加 break,continue 语句改变控制流,也支持在循环内部任意位置添加 return 语句,支持 return 不同长度 tuple 和不同类型的 Tensor。
使用样例:
# break 的使用样例
def break_usage(x):
tensor_idx = -1
for idx, val in enumerate(x) :
if val == 2.0 :
tensor_idx = idx
break # <------- jump out of while loop when break ;
return tensor_idx
当时输入 x = Tensor([1.0, 2.0 ,3.0]) 时,输出的 tensor_idx 是 Tensor([1])。
注:这里虽然 idx 是-1,但是返回值还是 Tensor。因为
tensor_idx
在 while loop 中转化为了Tensor
。
3.5 与、或、非¶
主要逻辑:
动转静模块支持将与、或、非三种运算符进行转换并动态判断,按照两个运算符 x 和 y 的不同,会有不同的语义:
如果运算符两个都是 Tensor,会组网静态图。
如果运算符都不是 Tensor,那么使用原始 python 语义。
如果一个是 Tensor,那么会走默认的 python 语义(最后还是 tensor 的运算符重载结果)。
注:若按照 paddle 的语义执行,与、或、非不再支持 lazy 模式,意味着两个表达式都会被 eval,而不是按照 x 的值来判断是否对 y 进行 eval。
使用样例:
def and(x, y):
z = y and x
return z
3.6 类型转换运算符¶
主要逻辑:
动态图中可以直接用 Python 的类型转化语法来转化 Tensor 类型。如若 x 是 Tensor 时,float(x)可以将 x 的类型转化为 float。
动转静在运行时判断 x 是否是 Tensor,若是,则在动转静时使用静态图cast
接口转化相应的 Tensor 类型。
使用样例:
def float_convert(x):
z = float(x)
return z
# 如果输入是 x = Tensor([True]) ,则 z = Tensor([1.0])
3.7 对一些 python 函数调用的转换¶
主要逻辑:
动转静支持大部分的 python 函数调用。函数调用都会被统一包装成为convert_xxx()
的形式,在函数运行期判别类型。若是 Paddle 类型,则转化为静态图的组网;反之则按照原来的 python 语义执行。常见函数如下:
print 函数 若参数是 Tensor,在动态图模式中 print(x)可以打印 x 的值。动转静时会转化为静态图的 Print 接口实现;若参数不是 Tensor,则按照 Python 的 print 语句执行。
len 函数 若 x 是 Tensor,在动态图模式中 len(x)可以获得 x 第 0 维度的长度。动转静时会转化为静态图 shape 接口,并返回 shape 的第 0 维。若 x 是个 TensorArray,那么 len(x)将会使用静态图接口
control_flow.array_length
返回 TensorArray 的长度;对于其他情况,会按照普通 Python len 函数运行。lambda 表达式 动转静允许写带有 Python lambda 表达式的语句,并且我们会适当改写使得返回对应结果。
函数内再调用函数(非递归调用) 对于函数内调用其他函数的情况,动转静会对内部的函数递归地进行识别和转写,以实现在最外层函数只需加一次装饰器即可的效果。
使用样例:
这里以 lambda 函数为例,展示使用方法
def lambda_call(x):
t = lambda x : x * x
z = t(x)
return z
# 如果输入是 x = Tensor([2.0]) ,则 z = Tensor([4.0])
不支持用法:
函数的递归调用
动转静暂不支持一个函数递归调用本身。原因是递归常常会用 if-else 构造停止递归的条件。此停止条件在静态图下只是一个 cond 组网,并不能在编译阶段得到递归条件的具体值,会导致函数运行时一直组网递归直至栈溢出。
def recur_call(x):
if x > 10:
return x
return recur_call(x * x) # < ------ 如果输入是 x = Tensor([2.0]) ,动态图输出为 Tensor([16]),静态图会出现调用栈溢出
3.8 List 和 Dict 容器¶
主要逻辑:
List : 若一个 list 的元素都是 Tensor,动转静将其转化为 TensorArray。静态图 TensorArray 仅支持 append,pop,修改操作,其他 list 操作(如 sort)暂不支持。若并非所有元素是 Tensor,动转静会将其作为普通 Python list 运行。
Dict : 动转静支持原生的 Python dict 语法。
注:List 不支持多重嵌套和其他的操作。具体错误案例见下面不支持用法。
使用样例:
def list_example(x, y):
a = [ x ] # < ------ 支持直接创建
a.append(x) # < ------ 支持调用 append、pop 操作
a[1] = y # < ------ 支持下标修改 append
return a[0] # < ------ 支持下标获取
不支持用法:
List 的多重嵌套
如 l = [[tensor1, tensor2], [tensor3, tensor4]]
,因为现在动转静将元素全是 Tensor 的 list 转化为 TensorArray,但 TensorArray 还不支持多维数组,因此这种情况下,动转静无法正确运行。遇到这类情况我们建议尽量用一维 list,或者自己使用 PaddlePaddle 的 create_array,array_read,array_write 接口编写为 TensorArray。
List 的其他的操作,例如 sort 之类
# 不支持的 list sort 操作
def sort_list(x, y):
a = [x, y]
sort(a) # < ----- 不支持,因为转化为 TensorArray 之后不支持 sort 操作。但是支持简单的 append,pop 和按下标修改
return a
3.9 paddle shape 函数¶
主要逻辑:
动转静部分支持 shape 函数:
【支持】当直接简单的使用 shape 时,可以正确获取 tensor 的 shape。
【不支持】当直接使用支持改变变量的 shape 后(例如 reshape 操作)调用其 shape 作为 PaddlePaddle API 参数。
如 x = reshape(x, shape=shape_tensor)
,再使用 x.shape[0] 的值进行其他操作。这种情况会由于动态图和静态图的本质不同而使得动态图能够运行,但静态图运行失败。其原因是动态图情况下,API 是直接返回运行结果,因此 x.shape 在经过 reshape 运算后是确定的。但是在转化为静态图后,因为静态图 API 只是组网,shape_tensor 的值在组网时是不知道的,所以 reshape 接口在组网完成后,静态图并不知道 x.shape 的值。PaddlePaddle 静态图用-1 表示未知的 shape 值,此时 x 的 shape 每个维度会被设为-1,而不是期望的值。同理,类似 expand 等更改 shape 的 API,其输出 Tensor 再调用 shape 也难以进行动转静。
使用样例:
def get_shape(x):
return x.shape[0]
不支持用法举例:
def error_shape(x, y):
y = y.cast('int32')
t = x.reshape(y)
return t.shape[0] # <------- 输入在 x = Tensor([2.0, 1.0]),y = Tensor([2])时,动态图输出为 2,而静态图输出为 -1 。不支持