动态图与静态图
Ascend
GPU
CPU
进阶
模型运行
概述
MindSpore支持两种运行模式:
静态图模式:又称Graph模式,将神经网络模型编译成一整张图,然后下发执行。该模式利用图优化、计算图整图下沉等技术提高运行性能,同时有助于规模部署和跨平台运行。
动态图模式:又称PyNative模式,将神经网络中的各个算子逐一下发执行,方便用户编写和调试神经网络模型。
Graph和PyNative两种模式的区别主要有:
使用场景:Graph模式需要一开始就构建好网络结构,然后框架做整图优化和执行,比较适合网络固定没有变化,且需要高性能的场景。而PyNative模式逐行执行算子,支持单独求梯度。
网络执行:Graph模式和PyNative模式在执行相同的网络和算子时,精度效果是一致的。由于Graph模式运用了图优化、计算图整图下沉等技术,Graph模式执行网络的性能和效率更高。
代码调试:在脚本开发和网络流程调试中,推荐使用PyNative模式进行调试。在PyNative模式下,可以方便地设置断点,获取网络执行的中间结果,也可以通过pdb的方式对网络进行调试。而Graph模式无法设置断点,只能先指定算子进行打印,然后在网络执行完成后查看输出结果。
默认情况下,MindSpore处于Graph模式,可以通过context.set_context(mode=context.PYNATIVE_MODE)
切换为PyNative模式;同样地,MindSpore处于PyNative模式时,可以通过 context.set_context(mode=context.GRAPH_MODE)
切换为Graph模式。
下面以Graph模式为例,演示MindSpore单算子、普通函数、模型的执行方式,并进一步说明如何在Graph模式和PyNative模式下进行性能优化及梯度求取。
执行方式
这里演示在Graph模式和PyNative模式下,单算子、普通函数、模型的执行方式。
在本案例的实际执行中,采取了MindSpore的默认方式
GRAPH_MODE
,用户也可以将其变更为PYNATIVE_MODE
进行尝试。
[ ]:
import numpy as np
import mindspore.nn as nn
from mindspore import context, Tensor, ParameterTuple, ms_function
import mindspore.ops as ops
from mindspore.common.initializer import Normal
from mindspore.nn import WithLossCell, Momentum
# 设定为Graph模式,也可替换为PYNATIVE_MODE
context.set_context(mode=context.GRAPH_MODE, device_target="Ascend")
执行单算子
[1]:
# 打印Conv2d算子的输出
conv = nn.Conv2d(3, 4, 3, bias_init='zeros')
input_data = Tensor(np.ones([1, 3, 5, 5]).astype(np.float32))
output = conv(input_data)
print(output.asnumpy())
[[[[-0.02593261 0.01615404 0.01615404 0.01615404 0.01196378]
[-0.01535788 0.05602208 0.05602208 0.05602208 0.04094065]
[-0.01535788 0.05602208 0.05602208 0.05602208 0.04094065]
[-0.01535788 0.05602208 0.05602208 0.05602208 0.04094065]
[-0.01409336 0.04544117 0.04544117 0.04544117 0.0373004 ]]
[[ 0.03874376 0.02201786 0.02201786 0.02201786 0.02687691]
[ 0.05751193 0.02690699 0.02690699 0.02690699 0.03515062]
[ 0.05751193 0.02690699 0.02690699 0.02690699 0.03515062]
[ 0.05751193 0.02690699 0.02690699 0.02690699 0.03515062]
[ 0.02599058 0.01130002 0.01130002 0.01130002 0.02304572]]
[[-0.00022919 0.02640852 0.02640852 0.02640852 0.04932421]
[ 0.01657246 0.0705748 0.0705748 0.0705748 0.0874946 ]
[ 0.01657246 0.0705748 0.0705748 0.0705748 0.0874946 ]
[ 0.01657246 0.0705748 0.0705748 0.0705748 0.0874946 ]
[ 0.03821789 0.09614976 0.09614976 0.09614976 0.10491695]]
[[ 0.0190958 0.02602289 0.02602289 0.02602289 0.01660084]
[ 0.03556763 0.06862713 0.06862713 0.06862713 0.02653556]
[ 0.03556763 0.06862713 0.06862713 0.06862713 0.02653556]
[ 0.03556763 0.06862713 0.06862713 0.06862713 0.02653556]
[ 0.00727296 0.04514674 0.04514674 0.04514674 0.01423099]]]]
执行普通函数
将若干算子组合成一个函数,然后直接通过函数调用的方式执行这些算子,并打印相关结果,如下例所示。
[2]:
def add_func(x, y):
z = ops.add(x, y)
z = ops.add(z, x)
return z
x = Tensor(np.ones([3, 3], dtype=np.float32))
y = Tensor(np.ones([3, 3], dtype=np.float32))
output = add_func(x, y)
print(output.asnumpy())
[[3. 3. 3.]
[3. 3. 3.]
[3. 3. 3.]]
执行网络
[3]:
import numpy as np
import mindspore.nn as nn
import mindspore.ops as ops
from mindspore import context, Tensor
class Net(nn.Cell):
def __init__(self):
super(Net, self).__init__()
self.mul = ops.Mul()
def construct(self, x, y):
return self.mul(x, y)
x = Tensor(np.array([1.0, 2.0, 3.0]).astype(np.float32))
y = Tensor(np.array([4.0, 5.0, 6.0]).astype(np.float32))
net = Net()
print(net(x, y))
[ 4. 10. 18.]
Graph模式说明
性能优化
在Graph模式下,MindSpore通过源码转换的方式,将Python的源码转换成IR,再在此基础上进行相关的图优化,最终在硬件设备上执行优化后的图。MindSpore使用的是一种基于图表示的函数式IR,即MindIR,采用了接近于ANF函数式的语义。Graph模式是基于MindIR进行编译优化的,编译器可以利用图优化、计算图整图下沉等技术对执行图进行更大程度的优化,从而获得更好的执行性能。
使用Graph模式时,需要使用nn.Cell
类并且在construct
函数中编写执行代码, 或者调用@ms_function
装饰器。
梯度求取
Graph模式中,定义了GradOperation计算神经网络的梯度,使用反向自动微分模式,即从正向网络的输出开始计算梯度。关于自动微分的详细信息,请参考自动微分。
PyNative模式说明
性能优化
正如文章开头所说,Graph模式适合高性能的场景,但PyNative模式中也提供了性能优化的手段。MindSpore提供了Staging功能,该功能可以在PyNative模式下将Python函数或者Python类的方法编译成计算图,通过图优化等技术提高运行速度,是一种混合运行机制。Staging功能的使用通过ms_function
装饰器达成,该装饰器会将模块编译成计算图,在给定输入之后,以图的形式下发执行。如下例所示:
[4]:
# 导入ms_function
from mindspore import ms_function
# 仍设定为PyNative模式
context.set_context(mode=context.PYNATIVE_MODE, device_target="Ascend")
add = ops.Add()
# 使用装饰器编译计算图
@ms_function
def add_fn(x, y):
res = add(x, y)
return res
x = Tensor(np.ones([4, 4]).astype(np.float32))
y = Tensor(np.ones([4, 4]).astype(np.float32))
z = add_fn(x, y)
print(z.asnumpy())
[[2. 2. 2. 2.]
[2. 2. 2. 2.]
[2. 2. 2. 2.]
[2. 2. 2. 2.]]
在加装了ms_function
装饰器的函数中,如果包含不需要进行参数训练的算子(如pooling
、add
等算子),则这些算子可以在被装饰的函数中直接调用,如上例所示。如果被装饰的函数中包含了需要进行参数训练的算子(如Convolution
、BatchNorm
等算子),则这些算子必须在被装饰的函数之外完成实例化操作。
[5]:
# Conv2d实例化操作
conv_obj = nn.Conv2d(in_channels=3, out_channels=4, kernel_size=3, stride=2, padding=0)
conv_obj.init_parameters_data()
@ms_function
def conv_fn(x):
res = conv_obj(x)
return res
input_data = np.random.randn(2, 3, 6, 6).astype(np.float32)
z = conv_fn(Tensor(input_data))
print(z.asnumpy())
[[[[ 0.00994964 0.01850731 -0.05146599]
[ 0.02427048 -0.09082688 -0.00945184]
[ 0.02710651 -0.07322617 0.02594434]]
[[ 0.00056772 -0.05043615 -0.03873939]
[-0.00445028 0.03694705 -0.03555503]
[ 0.07329068 -0.02026664 0.01922888]]
[[ 0.02257145 -0.04093865 -0.00493869]
[ 0.01740007 0.02478302 0.02072578]
[ 0.05831327 -0.03933404 0.01767443]]
[[-0.03954437 0.02160874 -0.00700614]
[ 0.03856367 -0.04015685 0.02508826]
[-0.0229507 -0.03803677 0.02813173]]]
[[[ 0.01678797 -0.02227589 -0.04470547]
[-0.05720481 -0.15464461 0.00911596]
[ 0.02566019 -0.04340314 0.03164666]]
[[ 0.03300299 -0.05849815 0.05841954]
[-0.11595733 -0.01524522 0.02947116]
[ 0.05930116 0.00831041 -0.0466827 ]]
[[-0.0797728 0.02910854 0.00766015]
[-0.01380327 -0.03338642 0.02625138]
[ 0.02279372 -0.00952736 0.02026749]]
[[ 0.04039776 -0.05340278 -0.0083563 ]
[ 0.04991922 -0.05205034 -0.0058607 ]
[ 0.00686666 0.00064385 0.00301326]]]]
梯度求取
PyNative模式中支持单独的梯度求取操作,下面演示如何利用这一特性调试网络模型。具体操作可通过GradOperation
求该函数或者网络所有的输入梯度。需要注意,输入类型仅支持Tensor。
构建网络如下。
[6]:
class LeNet5(nn.Cell):
"""
Lenet网络结构
"""
def __init__(self, num_class=10, num_channel=1):
super(LeNet5, self).__init__()
# 定义所需要的运算
self.conv1 = nn.Conv2d(num_channel, 6, 5, pad_mode='valid')
self.conv2 = nn.Conv2d(6, 16, 5, pad_mode='valid')
self.fc1 = nn.Dense(16 * 5 * 5, 120, weight_init=Normal(0.02))
self.fc2 = nn.Dense(120, 84, weight_init=Normal(0.02))
self.fc3 = nn.Dense(84, num_class, weight_init=Normal(0.02))
self.relu = nn.ReLU()
self.max_pool2d = nn.MaxPool2d(kernel_size=2, stride=2)
self.flatten = nn.Flatten()
def construct(self, x):
# 使用定义好的运算构建前向网络
x = self.conv1(x)
x = self.relu(x)
x = self.max_pool2d(x)
x = self.conv2(x)
x = self.relu(x)
x = self.max_pool2d(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
return x
# 实例化网络
net = LeNet5()
如上文所说,利用GradOperation
求函数输入的梯度。
[7]:
class GradWrap(nn.Cell):
"""求函数输入梯度"""
def __init__(self, network):
super(GradWrap, self).__init__(auto_prefix=False)
self.network = network
# 用Tuple的形式包装weight
self.weights = ParameterTuple(filter(lambda x: x.requires_grad, network.get_parameters()))
def construct(self, x, label):
weights = self.weights
# 返回值为梯度
return ops.GradOperation(get_by_list=True)(self.network, weights)(x, label)
在PyNative模式中进行网络训练。
[8]:
# 设定优化器、损失函数
optimizer = Momentum(filter(lambda x: x.requires_grad, net.get_parameters()), 0.1, 0.9)
criterion = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
# 通过WithLossCell获取Loss值
net_with_criterion = WithLossCell(net, criterion)
# 调用GradWrap
train_network = GradWrap(net_with_criterion)
train_network.set_train()
# 产生输入数据
input_data = Tensor(np.ones([32, 1, 32, 32]).astype(np.float32) * 0.01)
label = Tensor(np.ones([32]).astype(np.int32))
output = net(Tensor(input_data))
# 利用前向网络计算loss
loss_output = criterion(output, label)
# 求得梯度
grads = train_network(input_data, label)
# 输出loss
success = optimizer(grads)
loss = loss_output.asnumpy()
print(loss)
2.3025854
动静统一
MindSpore支持动态图和静态图两种模式,动态图通过解释执行,具有动态语法亲和性,表达灵活;静态图使用JIT编译优化执行,偏静态语法,在语法上有较多限制。由于动态图和静态图的编译流程不一致,两者的语法约束是不一致的。
动态图和静态图互相转换
在MindSpore中,我们可以通过控制模式输入参数来切换执行使用动态图还是静态图,通过context.set_context(mode=context.GRAPH_MODE)
可以设置静态图模式,通过context.set_context(mode=context.PYNATIVE_MODE)
可以设置成动态图模式。由于在静态图下,对于Python语法有所限制,因此从动态图切换成静态图时,需要符合静态图的语法限制,才能正确使用静态图来进行执行。MindSpore静态图的语法限制可以参考静态图语法限制。
动静结合
MindSpore支持在动态图下使用静态编译的方式来进行混合执行,通过使用ms_function修饰需要用静态图来执行的函数对象,即可实现动态图和静态图的混合执行,更多ms_function的使用可参考ms_function文档。
JIT Fallback
JIT Fallback是从静态图的角度出发考虑静态图和动态图的统一,希望静态图模式能够尽量多地支持动态图模式的语法,其借鉴了传统JIT编译的Fallback的思路。MindSpore默认使用静态图模式即Graph模式,不是所有的Python语法都能支持,用户在编写程序时容易遇到语法约束限制。通过JIT Fallback,用户可以灵活地进行静态图和动态图的切换。
当前JIT Fallback有条件地支持Graph模式的部分常量场景,包括调用第三方库、创建及使用Tensor、调用Python的print打印等。
代码用例如下:
[9]:
import numpy as np
import mindspore.nn as nn
from mindspore import context, Tensor
context.set_context(mode=context.GRAPH_MODE, device_target="GPU")
class Net(nn.Cell):
def construct(self):
x = np.array([1, 2, 3])
y = Tensor(x)
return y
net = Net()
print(net())
[1 2 3]