-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Description
需求描述 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