支持语法

一、主要针对场景

本文档概览性介绍了飞桨动转静功能的语法支持情况,旨在提供一个便捷的语法速查表,主要适用于如下场景

  1. 不确定当前动态图模型是否可以正确转化为静态图

  2. 转化过程中出现了问题但不知道如何排查

  3. 当出现不支持的语法时,如何修改源码适配动转静语法

若初次接触动转静功能,或对此功能尚不熟悉,推荐阅读:使用样例

若动静转换遇到了问题,或想学习调试的技巧,推荐阅读:报错调试

二、语法支持速查列表

分类 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_fnfalse_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 。不支持