Supported Grammars¶
Abstract¶
In this section we will talk about the supported grammars and unsupported grammars, also give some suggestions when the grammar is unsupported.
This article is mainly for the following scene :
Not sure whether the dynamic graph model can be converted into a static graph correctly.
There was a problem in the conversion process but don’t know how to troubleshoot.
How to modify the source code to adapt dynamic-to-static grammar when there is an unsupported grammar
If you are new to the dynamic-to-static module, or are not familiar with this function, you are recommended to read Introduction to dynamic and static documents ;
If you want to export model for prediction, or want to learn more about this module, you are recommended to read: Predictive Model Export Tutorial ;
If you encounter problems with @to_static, or want to learn about debugging skills, you are recommended to read Error Debugging Experience.
Supported Grammars¶
category | python grammar | is it supported ? | summary |
---|---|---|---|
Control flow keywords | if-else | Yes | Adaptively recognize and convert to cond Op, or keep python if-else |
while | Yes | Adaptively recognize and convert to While Op, or keep python if-else | |
for | Yes | Support iterative access to Tensor by for _ in x |
|
break continue |
Yes | Support putting break and continue at any position inside a loop |
|
return | Yes | Support putting return inside a loop |
|
operators | + - * / ** > < >= <= == |
Yes | Support by operator overloading |
and, or, not | Yes | 1.if both are tensors, convert to static graph. 2.if neither is tensor, degenerate to python operator. 3. If only one is tensor, degenerate to python operator, but the results is correct. |
|
Type conversion operator | Yes | Convert to paddle.cast if operand is tensor |
|
Paddle shape | Tensor.shape() | Partial support | Support obtaining shape information, but there may be -1 |
python functions | print(x) | Yes | Convert to PrintOp if operand is tensor |
len() | Yes | Return the shape[0] if operand is tensor. |
|
lambda expression | Yes | Use convert_call to convert lambda function |
|
function call | Yes | Use convert_call to convert function recursively |
|
recursively call | No | While converting, converted function can't get value from static tensor | |
list sort | No | Lists will be converted to TensorArray and TensorArray don't support sort |
|
Errors and Exceptions | assert | Yes | Convert to Assert if operand is tensor |
Python containers | list | Partial support | Lists will be converted to TensorArray . Only append , pop , index is supported |
dict | Yes | @to_static will add the Tensors in a dict into PaddlePaddle static graph Program , so dict is supported by @to_static. |
|
Third party library | numpy | Partial support | We suggest to use PaddlePaddle APIs to replace numpy API in this case. |
Details¶
if-else¶
principle :¶
While in the dynamic graph, the code is interpreted and executed line by line, so the value of condition variables used by if
are determined, which means that the logic branch of False will not be executed.
However, in the static graph, the control flow is realized through the cond
operators. Each branch is represented by true_fn
and false_fn
respectively . Under this circumstance, the false_fn
will be executed to build the computation graph.
When the condition variables in If
are Tensor
, if-else
will be transformed to a cond
operators.
When the condition variables in If
aren’t Tensor
, if-else
will be executed as a python if-else
code.
Note: When the condition variables in
If
areTensor
, thetensor.numel()
must equal to 1.
Error correction guide:¶
When using
if
statement, please determine whether the type of condition variable isPaddle.Tensor
. If it is not a Tensor type, it will be executed according to the normal python logic and will not be converted into a static graph.Check the
numel()
of the condition variable is 1 .
while¶
principle :¶
When the condition variable in the while
loop is Tensor
, the while statement will be converted into the while_loop
API statement in the static graph, otherwise it will run as a normal Python while.
Note: When the condition variables in
If
areTensor
, thetensor.numel()
must equal to 1.
Error correction guide:¶
The same as if-elif-else
for loops¶
principle :¶
For loops
have different semantics according to different usage. Normally, the use of for loops
can be divided into the following categories:
for _ in range(len)
: The statement will be converted into an equivalent Python while loop, and then the dynamic and static conversion will be performed according to the logic of the while loop.for _ in x
: When x is a python container or iterator, it will run with normal Python logic. When x isTensor
, it will be converted to obtainx[0]
,x[1]
,… in sequence.for idx, val in enumerate(x)
: When x is a Python container or iterator, it will run with normal python logic. When x is aTensor
, idx will be converted into a 1-D Tensor with value 0, 1, … in sequence. val will be converted intox[0]
,x[1]
, … every time in the loop.
In views of implementation, the for-loop
will eventually be transformed into the corresponding while statement, and then use WhileOp
for static graph.
examples :¶
def ForTensor(x):
"""Fetch element in x and print the square of each x element"""
for i in x :
print (i * i)
#usage: ForTensor(paddle.to_tensor(x))
return / break / continue¶
principle :¶
The current dynamic-to-static supports adding break and continue statements in for, while loops to change the control flow. It also supports adding return statements at any position inside the loop, and supports return tuples with different lengths and different dtypes
of Tensor
.
examples :¶
# break usage example :
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
When you execute
paddle.jit.to_static(break_usage)(paddle.to_tensor([1.0, 2.0, 3.0])
the tensor_idx is Tensor([1])
Note : Although idx is integer here, the return value is still Tensor. Because tensor_idx is converted into Tensor in the while loop.
and / or / not¶
principle :¶
The dynamic-to-static module supports the conversion of and, or, and non-operators . According to the types of the two operands x
and y
, there will be different semantics:
If both x, y are tensors, this statement will be converted to static graph.
If neither x, y is tensor, this statement will degenerate to python operator.
If only one of them is tensor, this statement will degenerate to python operator, but the results are always correct.
Note : If executed according to the semantics of paddle,
and
or
, andnot
no longer support the lazy mode, it means that both expressions will be evaluated, instead of evaluating y according to the value of x.
examples :¶
def and(x, y):
z = y and x
return z
Type conversion operator¶
principle :¶
In dynamic graphs, you can directly use python’s type conversion to convert Tensor types. For example, if x is a Tensor, float(x) can convert the data type of x to float.
Dynamic-to-static module will judge whether x is a Tensor
at runtime. If so, use the static graph paddle.cast
interface to convert to the target data type.
examples :¶
def float_convert(x):
z = float(x)
return z
# if the x = Tensor([True]) ,then z = Tensor([1.0])
python function calls¶
Most circumstances of python function call is supported. The function calls will be converted into the form of convert_xxx()
, and the data type of arguments will be determined during running the function. If some of arguments is Tensor
, it will be transformed into a static computation graph; otherwise, it will be executed according to the original python semantics.
Some common functions are illustrated:
print (x)
If the parameter is Tensor,print(x)
can print the value of x in dynamic graph mode.While in dynamic-to-static graph model, It will be converted into aPrint
call. If the parameter is not Tensor, it will be executed according to python’s print statement.len (x)
If the parameter is Tensor,len(x)
can get the length of the 0th dimension of tensor x. While in dynamic-to-static graph model, It will be converted into acontrol_flow.array_length
call. If the parameter is not Tensor, it will be executed according to python’s print statement.lambda
Theto_static
function will callconvert_call
to convert lambda function as it’s a normal function.function call (not recursively) The
to_static
function will callconvert_call
to convert called function as it’s a normal function. Just put@to_static
in the top-level function once.
examples :¶
def lambda_call(x):
t = lambda x : x * x
z = t(x)
return z
# if the x is Tensor([2.0]) ,then z equals to Tensor([4.0]).
unsupported usage¶
recursively call While converting, converted function can’t get value from static tensor.
def recur_call(x):
if x > 10:
return x
return recur_call(x * x) # < ------ If x = Tensor([2.0]) ,in dygraph mode the output is Tensor([16]),while in dygraph-to-static graph mode call stack overflows
list / dict¶
principle :¶
list: if all elements in a list are Tensors, then @to_static converts it to TensorArray. PaddlePaddle static graph TensorArray supports append, pop, and modify, other list operations such as sort cannot be supported. When not all elements in a list are Tensors, @to_static will treat it as normal Python list.
dict: @to_static will add the Tensors in a dict into PaddlePaddle static graph Program, so dict is supported by @to_static.
def list_example(x, y):
a = [ x ] # < ------ supported
a.append(x) # < ------ supported
a[1] = y # < ------ supported
return a[0] # < ------ supported
Note: List does not support multiple nesting and other operations. For specific error cases, see below.
unsupported usage¶
multiple nesting
For example: l = [[tensor1, tensor2], [tensor3, tensor4]], because @to_static transformed a list whose elements are all Tensors into PaddlePaddle static graph TensorArray, but TensorArray doesn’t support multi-dimensions, @to_static cannot run this case.
We suggest to use 1-D list at most time, or use PaddlePaddle API create_array, array_read, array_write to control TensorArray.
complex operators, such as sort
def sort_list(x, y):
a = [x, y]
sort(a) # < ----- unsupported
return a
paddle.shape¶
principle :¶
partial supported
Support simple usage of
shape
, such as get the shape of a tensor.Don’t support get shape after a reshape operators. You may get a -1 in shape value.
For example, x = reshape(x, shape=shape_tensor)
, then use x.shape[0]
to do other operation. Due to the difference between dynamic and static graph, it is okay in dynamic but it will fail in static graph. The reason is that APIs return computation result in dynamic graph mode, so x.shape has deterministic value after calling reshape . However, static graph doesn’t have the value shape_tensor during building network, so PaddlePaddle doesn’t know the value of x.shape after calling reshape. PaddlePaddle static graph will set -1 to represent unknown shape value for each dimension of x.shape in this case, not the expected value. Similarily, calling the shape of the output tensor of those APIs which change the shape, such as expend, cannot be converted into static graph properly.
examples :¶
def get_shape(x):
return x.shape[0] # <---- supported
def error_shape(x, y):
y = y.cast('int32')
t = x.reshape(y)
return t.shape[0] # <------- don't supported ; if x = Tensor([2.0, 1.0]),y = Tensor([2]),in dygraph mode the output is 2,while in dygraph-to-static graph mode the output is -1.