Skip to content

【Feature Request】 PyLayer 功能支持动转静 @to_static 🚀 #54120

@Aurelius84

Description

@Aurelius84

需求描述 Feature Description

一、任务目标

请描述你正在做的项目是什么,如模型、论文、项目是什么?

期望飞桨的动态图中的 PyLayer 机制能够与飞桨的动转静@to_staitc 互通,支持模型中 PyLayer 的自定义层能够被 @to_static 感知并正确地生成静态图 Program,支撑转静训练导出推理

二、需求场景

请描述你的项目中为什么需要用此功能

飞桨的动态图 PyLayer 向用户提供了一种「高度灵活且便利」的自定义网络层前、反向计算的机制,比如存在一些用户自定义的计算逻辑,无法通过飞桨 现有的某个 API 或某些 API 组合实现,故可以借助 PyLayer 来实现用户的「idea」。如下是官网给出的一个样例

import paddle
from paddle.autograd import PyLayer

# Inherit from PyLayer
class cus_tanh(PyLayer):
    @staticmethod
    def forward(ctx, x, func1, func2=paddle.square):
        # ctx is a context object that store some objects for backward.
        ctx.func = func2
        y = func1(x)
        # Pass tensors to backward.
        ctx.save_for_backward(y)
        return y

    @staticmethod
    # forward has only one output, so there is only one gradient in the input of backward.
    def backward(ctx, dy):
        # Get the tensors passed by forward.
        y, = ctx.saved_tensor()
        grad = dy * (1 - ctx.func(y))
        # forward has only one input, so only one gradient tensor is returned.
        return grad


data = paddle.randn([2, 3], dtype="float64")
data.stop_gradient = False
z = cus_tanh.apply(data, func1=paddle.tanh)
z.mean().backward()

print(data.grad)

目前,当动态图模型中包含 PyLayer 接口功能的使用时,暂不支持 @to_static 装饰以生成对应的静态图 Program,即 @to_static 暂时无法感知和处理 PyLayer 中 forward()backward() 函数中的计算逻辑。

意味着,包含 PyLayer 的用户动态图模型,无法复用转静训练的「加速能力」,以及「导出推理」的功能。

三、功能描述

3.1 整体功能

飞桨的动态图中的 PyLayer 机制能够与飞桨的动转静@to_staitc 互通,支持模型中 PyLayer 的自定义层能够被 @to_static 感知并正确地生成静态图 Program,支撑转静训练导出推理

如下是一个功能支持后的全流程样例:

from symbol import parameters
import paddle
from paddle.autograd import PyLayer
from paddle.jit import to_static

# Inherit from PyLayer
class cus_tanh(PyLayer):
    @staticmethod
    def forward(ctx, x, func1, func2=paddle.square):
        # ctx is a context object that store some objects for backward.
        ctx.func = func2
        y = func1(x)
        # Pass tensors to backward.
        ctx.save_for_backward(y)
        return y

    @staticmethod
    # forward has only one output, so there is only one gradient in the input of backward.
    def backward(ctx, dy):
        # Get the tensors passed by forward.
        y, = ctx.saved_tensor()
        grad = dy * (1 - ctx.func(y))
        # forward has only one input, so only one gradient tensor is returned.
        return grad

class SimpleNet(paddle.nn.Layer):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.linear = paddle.nn.Linear(4, 8)
    
    @to_static
    def forward(self, x):
        y = self.linear(x)
        out = cus_tanh.apply(y, func1=paddle.tanh)
        out = paddle.mean(out)

        return out


def train(net, opt):
    batch_num = 10
    for i in range(batch_num):
        data = paddle.randn([2, 4])
        out = net(data)

        out.backward()
        opt.step()
        opt.clear_grad()
        print("loss: ", out.item())
    
    save(net)


def save(net):
    path = "simple_net"
    x_spec = paddle.static.InputSpec(shape=[-1, 4], dtype='float32', name='x')
    paddle.jit.save(net, path, input_spec=[x_spec])


if __name__ == '__main__':
    net = SimpleNet()
    sgd = paddle.optimizer.SGD(0.001, parameters=net.parameters())
    train(net, sgd)

PyLayer 在使用上需要遵循一定的规范,如:

  • 子类必须包含静态的 forward 和 backward 函数,它们的第一个参数必须是 PyLayerContext
  • 如果 backward 的某个返回值在 forward 中对应的 Tensor 是需要梯度,这个返回值必须为 Tensor
  • backward 输入的 Tensor 的数量必须等于 forward 输出 Tensor 的数量
  • 如果你需在 backward 中使用 forward 的输入 Tensor ,你可以将这些 Tensor 输入到 PyLayerContext 的 save_for_backward 方法,之后在 backward 中使用这些 Tensor 。
  • backward 的输出 Tensor 的个数等于 forward 输入 Tensor 的个数

3.2 初期锚点

为了更好新增此 Feature,初期可以假设forward、backward 函数中都是基于Tensor的计算,且调用都是 Paddle API。,以此为「初期功能」的锚点,可以更专注于 Feature 的技术方案设计。

如下是初期锚点对应的一个简单样例:

from symbol import parameters
import paddle
from paddle.autograd import PyLayer
from paddle.jit import to_static

# Inherit from PyLayer
class cus_tanh(PyLayer):
    @staticmethod
    def forward(ctx, x): 
        # ctx is a context object that store some objects for backward.
        y = paddle.tanh(x)       # <------ 仅仅包含 Paddle API 的计算
        # Pass tensors to backward.
        ctx.save_for_backward(y)
        return y

    @staticmethod
    # forward has only one output, so there is only one gradient in the input of backward.
    def backward(ctx, dy):
        # Get the tensors passed by forward.
        y = ctx.saved_tensor()
        grad = dy * (1 - paddle.square(y))   # <------ 仅仅包含 Paddle API 的计算
        # forward has only one input, so only one gradient tensor is returned.
        return grad

class SimpleNet(paddle.nn.Layer):
    # .... (同上)

def train(net, opt):
    # .... (同上)

def save(net):
    # .... (同上)

if __name__ == '__main__':
    # .... (同上)

3.3 进阶考虑

在满足 「初期锚点」功能的技术方案设计后,可以再进阶考虑如果 forward 和 backward 函数中包含了其他更为高阶的用法,比如:

  • 参数列表中不仅只包含了 Tensor,还包含了非 Tensor 对象
  • forward 、 backward 函数调用了其他用户自定义的函数(这些函数里调用了Paddle API)

附:替代实现 Alternatives

PyLayer 功能本质上是为了支持用户「自定义」计算逻辑,解决飞桨现有 API 体系无法直接组合实现的问题(尤其是反向自定义)。

在飞桨框架中,存在一个等价的功能「自定义算子」是可以替换掉 PyLayer 的,且「自定义算子」功能是支持 动转静 @to_static 训练和导出推理的。

具体的用法可以参考飞桨官网:https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/guides/custom_op/index_cn.html

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions