静态子图优化:用法

注释

这是一个实验性的特性,因此API在未来的开发过程中可能会发生变化。

此功能旨在通过优化执行来提高运行时性能模型中的静态子图。启用此功能后,第一次迭代正常运行,但也会收集执行跟踪。这个然后使用跟踪生成优化的代码,这些代码将被调用,而不是define-by-run从第二次迭代开始的代码。

链式统计图

标记链条的装饰器__调用__()作为静态子图。

基本用法

要启用静态图形优化,只需添加链接器.static_graph()链的装饰器__调用__()方法。现在我们将展示如何可以修改Chainer MNIST示例以使用此功能。修改后的版本使用静态子图优化位于示例/staticgraph_optimizations/mnist.

第一步是导入必要的包:

列车管理.py
24 链子 进口 静态代码
25 链子 进口 统计图

由于神经网络模型MLP公司对应于静态图,我们可以通过以下方式将其注释为静态图使用链接器.static_graph()链上的装饰器__调用__()方法。这让框架知道链的定义-运行代码总是创建相同的图(也就是说,它总是执行相同的操作计算序列)。我们将这种链称为静态链条在里面文档。

列车管理.py
34#网络定义
35 MLP公司(链子.链条):
36
37    “”“用于数字分类的全连接神经网络。
38
39"""
40
41    定义 __初始化__(自己, n个单位, n出):
42        超级的(MLP公司, 自己).__初始化__()
43        具有 自己.初始化作用域():
44            #将推断每个层的输入大小
45            自己.l1级 = L(左).线性的(, n个单位)  #n英寸->n单位
46            自己.第二语言 = L(左).线性的(, n个单位)  #n_单位->n_单位
47            自己.l3级 = L(左).线性的(, n出)  #n个单位->n个输出
48
49    @统计图
50    定义 __呼叫__(自己, x):
51        小时1 = F类.relu公司(自己.l1级(x))
52        氢气 = F类.relu公司(自己.第二语言(小时1))
53        返回 自己.l3级(氢气)

注释

如果模型的定义运行代码有任何可能导致其调用的控制流操作每次调用不同的Chainer函数/链接,则无法使用此装饰器。

注释

目前,对于如何将变量传递到静态链中有一些限制__调用__()方法。请参阅文档链接器.static_graph()了解详细信息。

回想一下,静态链的define by run代码__调用__()方法只在第一次迭代,然后被优化的静态调度代码取代。仅当前实现知道如何对Chainer函数和链接的调用进行自动重新放置。任何其他代码用户输入__调用__()(我们称之为“副作用代码”)只会被调用一次默认情况下,因为define-by-run代码是仅在第一次迭代期间执行。为了确保这种“副作用”代码实际得到调用每个迭代时,我们需要将其放在由static_code().我们预计很少需要使用副作用代码,但为了完整性使用它的模型在MLPSide效果静态图链MNIST示例。

在这个例子中,我们只需要使用链接器.static_graph()在模型链上,因为整个模型是静态的。然而,在更一般的动态模型中,每个最大的静态子图(应该是写为链)也应使用链接器.static_graph().

注释

的嵌套应用程序链接器.static_graph()不允许。也就是说,如果链接器.static_graph()-装饰链调用另一个链,只有最外层的链应该使用decorator。

在同一迭代中多次调用静态链

在一般的动态图形网络中,这是不可能的提前知道静态链在任何特定迭代中将被调用多少次。请注意,在培训期间,有必要保持单独的内部状态(例如中间状态激活),以便可以在向后传递中计算渐变。因此,尽管静态时间表的层功能每次都是相同的调用静态链时,任何内部状态都必须是不同的。也可能是静电在同一迭代中,可以使用不同形状和/或类型的输入多次调用chain。为了避免混淆,“静态时间表”将同时指函数和任何相应的内部状态例如激活。

如果禁用反向传播模式(chainer.config.enable_backprop链接器False(错误)),实现只需计算第一次调用的静态调度,并将其重新用于后续调用,前提是缓存的调度与输入形状/类型兼容。然而,在培训期间,为了计算,有必要为每个调用维护不同的内部状态后向传球的坡度,这使我们无法为每一次重复使用相同的静态时间表迭代中静态链的多次调用。

当前的实现按如下方式处理此问题。静态调度的缓存,最初是空,与每个静态链关联。此缓存的大小将等于在任何以前的迭代中调用静态链的次数,并且每当某些链配置标志发生变化,例如训练模式和反向传播模型。在开始时在给定的迭代中,所有缓存的调度都可以使用,并且可用调度的数量每次调用静态链时都会递减。如果在缓存大小为零时调用链,然后它的define-by-run代码将执行以创建新的调度缓存。

为了使这种实现工作,必须在转发时通知每个静态链已结束(或在开始转发时),以便可以使用所有缓存的计划再一次。在当前的实现中,这是通过调用向后()损失处理方法模型中的变量。这将用于处理典型用例。然而,在某些模型中,可能需要在调用之前执行多次转发向后()。在这种情况下,向静态链签名前向传递(和迭代)已结束,调用my_chain.schedule_manager.end_forward().这个日程管理器静态链的属性是名为StaticSchedule函数这将在调用链后可用。

对模型调试的影响

注意,由于静态链中的代码__调用__()仅在在第一次迭代中,您只能在第一次迭代。假设链条实际上是静态的,在第一阶段,它的定义运行代码中的任何问题都应该很明显迭代,在以后的迭代中不需要调试此代码。然而,此功能确实提供了一些功能来帮助调试。例如,可以获取并检查当前的静态时间表。如果您希望(通过调试转发()方法StaticSchedule函数在里面统计图).

禁用静态子图优化

可以通过设置链接器配置.use_static_graphFalse(错误).如果设置为False(错误),的链接器.static_graph()decorator只需调用包装好的函数,而不会产生任何进一步的副作用。

局限性和未来工作

  • 优化开关可以让用户在运行时性能和内存使用之间进行权衡:当前的实现主要通过减少需要运行的Python代码数量来实现加速,但尚未实现内存使用或运行时性能的高级优化。理想情况下,用户应该能够调整性能调优参数,以控制内存消耗和运行时性能之间的平衡。

  • 与GRU和LSTM链接不兼容:此功能要求链的所有输入变量都必须显式出现在链的参数中__调用__()方法。然而,具有状态的GRU和LSTM链接为RNN状态变量维护链的变量属性。正在考虑对设计进行更改以支持此类链接和/或对这些链接进行修改。只要相应的RNN在静态链中展开,这些链接仍可用于当前实现。有关此示例,请参阅上修改的ptb示例示例/staticgraph_optimizations/ptb

  • 内存使用率:当前实现缓存所有静态调度,在某些情况下可能导致内存使用率过高。例如,当训练模式或迷你背带尺寸发生变化时,会创建单独的时间表。

  • 高级图形优化:高级优化(如操作融合)尚未实现。

  • 对静态链的参数的约束:当前版本要求内部使用的所有输入变量__调用__()静态链的定义必须出现在该方法的参数中,或者在按定义运行的代码中定义。此外,出现在参数列表中的任何变量都必须单独出现,或者包含在列表或元组中。允许任意级别的嵌套。

  • 模型导出:如果模型的完整计算图是静态的,原则上应该可以以可在其他平台和语言上运行的格式导出静态调度。此功能的另一个原始动机是支持导出静态Chainer模型以在C/C++上运行和/或优化Cython/C/C++中的静态调度执行代码。然而,ONNX似乎正在实现这一目标,Chainer已经在开发一个单独的ONNX出口商。也许这两个特性可以在将来的某个时候合并。

  • 双向后支持:此功能旨在支持双向后(梯度),但尚未测试。

  • 不支持ChainerX。如果您有使用此功能编写的代码,但希望使用ChainerX运行模型,请设置链接器配置.use_static_graph配置到False(错误)。代码应该可以在没有任何其他更改的情况下工作。

示例

有关使用此功能的其他示例,请参阅中的示例示例/静态图优化.