使用流程控制语句

Ascend GPU CPU 模型开发

查看源文件

概述

MindSpore流程控制语句的使用与Python原生语法相似,尤其是在PYNATIVE_MODE模式下, 与Python原生语法基本一致,但是在GRAPH_MODE模式下,会有一些特殊的约束。鉴于以上原因,下述关于流程控制语句的使用指导均是指在GRAPH_MODE模式下运行。

使用流程控制语句时,MindSpore会依据条件是否为变量来决策是否在网络中生成控制流算子,只有条件为变量时网络中才会生成控制流算子。如果条件表达式的结果需要在图编译时确定,则条件为常量,否则条件为变量。需要特殊说明的是,当网络中存在控制流算子时,网络会被切分成多个执行子图,子图间进行流程跳转和数据传递会引起一定的性能损耗。

条件为变量的场景:

  • 条件表达式中存在Tensor或者元素为Tensor类型的List、Tuple、Dict,并且条件表达式的结果受Tensor的值影响。

常见的变量条件:

  • (x < y).all()xy为算子输出。此时条件是否为真取决于算子输出xy,而算子输出是图在各个step执行时才能确定。

  • x in listx为算子输出。

条件为常量的场景:

  • 条件表达式中不存在Tensor和元素为Tensor类型的List、Tuple、Dict。

  • 条件表达式中存在Tensor或者元素为Tensor类型的List、Tuple、Dict,但是表达式结果不受Tensor的值影响。

常见的常量条件:

  • self.flagself.flag为标量。此处self.flag为一个bool类型标量,其值在构建Cell对象时已确定,因此该条件是一个常量条件。

  • x + 1 < 10x为标量。此处x + 1的值在构建Cell对象时是不确定的,但是在图编译时MindSpore会计算所有标量表达式的结果,因此该表达式的值也是在编译期确定的,该条件为常量条件。

  • len(my_list) < 10my_list为元素是Tensor类型的List对象。虽然该条件表达式包含Tensor,但是表达式结果不受Tensor的值影响,只与my_list中Tensor的数量有关,因此该条件为常量条件。

  • for i in range(0,10)i为标量,潜在的条件表达式i < 10为常量条件。

使用if语句

使用if语句需要注意在条件为变量时,在不同分支中的同一变量名应被赋予相同的数据类型。同时,网络最终生成的执行图的子图数量与if的数量成正比关系,过多的if会产生较大的控制流算子性能开销和子图间数据传递性能开销。

使用条件为变量的if语句

在例1中,out在true分支被赋值为[0],在false分支被赋值为[0, 1],条件x < y为变量,因此在out = out + 1这一句无法确定输入out的数据类型,会导致图编译出现异常。

例1:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class SingleIfNet(nn.Cell):
    def construct(self, x, y, z):
        if x < y:
            out = x
        else:
            out = z
        out = out + 1
        return out

forward_net = SingleIfNet()
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
z = Tensor(np.array([0, 1]), dtype=ms.int32)
output = forward_net(x, y, z)

例1报错信息如下:

ValueError: mindspore/ccsrc/pipeline/jit/static_analysis/static_analysis.cc:734 ProcessEvalResults] The return values of different branches do not match. Shape Join Failed: shape1 = (2), shape2 = ()..

使用条件为常量的if语句

在例2中,out在true分支被赋值为标量0,在false分支被赋值为[0, 1],xy均为标量,条件x < y + 1为常量,图编译阶段可以确定是走true分支,因此网络中只存在true分支的内容并且无控制流算子,out = out + 1的输入out数据类型是确定的,因此该用例可正常执行。

例2:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class SingleIfNet(nn.Cell):
    def construct(self, z):
        x = 0
        y = 1
        if x < y + 1:
            out = x
        else:
            out = z
        out = out + 1
        return out

forward_net = SingleIfNet()
z = Tensor(np.array([0, 1]), dtype=ms.int32)
output = forward_net(z)

使用for语句

for语句会展开循环体内容。在例3中,for循环了3次,与例4最终生成的执行图结构是完全一致的,因此使用for语句的网络的子图数量、算子数量取决于for的迭代次数,算子数量过多或者子图过多会导致硬件资源受限。for语句导致出现子图过多的问题时,可参考while写作方式,尝试将for语句等价转换为条件是变量的while语句。

例3:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class IfInForNet(nn.Cell):
    def construct(self, x, y):
        out = 0
        for i in range(0,3):
            if x + i < y :
                out = out + x
            else:
                out = out + y
            out = out + 1
        return out

forward_net = IfInForNet()
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y)

例4:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class IfInForNet(nn.Cell):
    def construct(self, x, y):
        out = 0
        #######cycle 0
        if x + 0 < y :
            out = out + x
        else:
            out = out + y
        out = out + 1

         #######cycle 1
        if x + 1 < y :
            out = out + x
        else:
            out = out + y
        out = out + 1

         #######cycle 2
        if x + 2 < y :
            out = out + x
        else:
            out = out + y
        out = out + 1
        return out

forward_net = IfInForNet()
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y)

使用while语句

while语句相比for语句更为灵活。当while的条件为常量时,while对循环体的处理和for类似,会展开循环体里的内容。当while的条件为变量时,while不会展开循环体例的内容,则会在执行图产生控制流算子。

使用条件为常量的while语句

如例5所示,条件i < 3为常量, while的循环体的内容会被复制3份,因此最终生成的执行图和例4完全一致。while语句条件为常量时,算子数量和子图数量与while的循环次数成正比,算子数量过多或者子图过多会导致硬件资源受限。

例5:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class IfInWhileNet(nn.Cell):
    def construct(self, x, y):
        i = 0
        out = x
        while i < 3:
            if x + i < y :
                out = out + x
            else:
                out = out + y
            out = out + 1
            i = i + 1
        return out

forward_net = IfInWhileNet()
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y)

使用条件为变量的while语句

如例6所示,把while条件变更为变量,while按照不展开处理,最终网络输出结果和例5一致,但执行图的结构不一致。例6不展开的执行图,有较少的算子和较多的子图,会使用较短的编译时间和占用较小的设备内存,但是会产生额外的控制流算子执行和子图间数据传递引起的性能开销。

例6:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class IfInWhileNet(nn.Cell):
    def construct(self, x, y, i):
        out = x
        while i < 3:
            if x + i < y :
                out = out + x
            else:
                out = out + y
            out = out + 1
            i = i + 1
        return out

forward_net = IfInWhileNet()
i = Tensor(np.array(0), dtype=ms.int32)
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y, i)

while的条件为变量时,while循环体不能展开,while循环体内的表达式都是在各个step运行时计算,因此循环体内部不能出现标量、List、Tuple等非Tensor类型的计算操作,这些类型计算操作需要在图编译时期完成,与while在执行期进行计算的机制是矛盾的。如例7所示,条件i < 3是变量条件,但是循环体内部存在j = j + 1的标量计算操作,最终会导致图编译出错。

例7:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms

class IfInWhileNet(nn.Cell):
    def __init__(self):
        super().__init__()
        self.nums = [1, 2, 3]

    def construct(self, x, y, i):
        j = 0
        out = x
        while i < 3:
            if x + i < y :
                out = out + x
            else:
                out = out + y
            out = out + self.nums[j]
            i = i + 1
            j = j + 1
        return out

forward_net = IfInWhileNet()
i = Tensor(np.array(0), dtype=ms.int32)
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y, i)

例7报错信息如下:

IndexError: mindspore/core/abstract/prim_structures.cc:178 InferTupleOrListGetItem] list_getitem evaluator index should be in range[-3, 3), but got 3.

while条件为变量时,循环体内部不能更改算子的输入shape。因为MindSpore要求网络的同一个算子的输入shape在图编译时是确定的,而在while的循环体内部改变算子输入shape的操作是在图执行时生效,两者是矛盾的。如例8所示,条件i < 3为变量条件,while不展开,循环体内部的ExpandDims算子会改变表达式out = out + 1在下一轮循环的输入shape,会导致图编译出错。

例8:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore.common import dtype as ms
from mindspore import ops

class IfInWhileNet(nn.Cell):
    def __init__(self):
        super().__init__()
        self.expand_dims = ops.ExpandDims()

    def construct(self, x, y, i):
        out = x
        while i < 3:
            if x + i < y :
                out = out + x
            else:
                out = out + y
            out = out + 1
            out = self.expand_dims(out, -1)
            i = i + 1
        return out

forward_net = IfInWhileNet()
i = Tensor(np.array(0), dtype=ms.int32)
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = forward_net(x, y, i)

例8报错信息如下:

ValueError: mindspore/ccsrc/pipeline/jit/static_analysis/static_analysis.cc:734 ProcessEvalResults] The return values of different branches do not match. Shape Join Failed: shape1 = (1, 1), shape2 = (1)..

约束

当前使用流程语句除了条件变量场景下的约束,还有一些其他特定场景下的约束。

副作用约束

在使用条件为变量的流程控制语句时,图编译生成的网络模型中会包含控制流算子,在此场景下,正向图会执行两次。如果此时正向图中存在Assign等副作用算子并且是训练场景时,会导致反向图计算结果与预期不符。

如例9所示,期望x的梯度为2,但是实际执行得到的梯度为3,原因是正向图执行了两次,tmp = self.var + 1self.assign(self.var, tmp)被执行了两次,out = (self.var + 1) * x实际上是out = (2 + 1) * x,最终导致梯度结果出错。

例9:

import numpy as np
from mindspore import context
from mindspore import Tensor, nn
from mindspore import dtype as ms
from mindspore import ops
from mindspore.ops import composite
from mindspore import Parameter

class ForwardNet(nn.Cell):
    def __init__(self):
        super().__init__()
        self.var = Parameter(Tensor(np.array(0), ms.int32))
        self.assign = ops.Assign()

    def construct(self, x, y):
        if x < y:
            tmp = self.var + 1
            self.assign(self.var, tmp)
        out = (self.var + 1) * x
        out = out + 1
        return out

class BackwardNet(nn.Cell):
    def __init__(self, net):
        super(BackwardNet, self).__init__(auto_prefix=False)
        self.forward_net = net
        self.grad = composite.GradOperation()

    def construct(self, *inputs):
        grads = self.grad(self.forward_net)(*inputs)
        return grads

forward_net = ForwardNet()
backward_net = BackwardNet(forward_net)
x = Tensor(np.array(0), dtype=ms.int32)
y = Tensor(np.array(1), dtype=ms.int32)
output = backward_net(x, y)
print("output:", output)

执行结果:

output: 3

控制流训练场景不支持的副作用算子列表如下:

Side Effect List

Print

Assign

AssignAdd

AssignSub

ScalarSummary

ImageSummary

TensorSummary

HistogramSummary

ScatterAdd

ScatterDiv

ScatterMax

ScatterMin

ScatterMul

ScatterNdAdd

ScatterNdSub

ScatterNdUpadte

ScatterNonAliasingAdd

ScatterSub

ScatterUpdate

死循环约束

当表达式while condcond的值恒为标量True时,无论循环内部是否存在breakreturn循环退出语句,均可能会出现未知异常。