Created by: MrChengmo
PaddleRec Trainer重构设计
整合重复代码
之前存在的问题
- 代码冗余: 如
single_train/single_infer/cluster
中大量相同的代码段 - 修改不便,发现一处bug,得同时修多个trainer
因此,首先做的工作是,将single_train/single_infer/cluster
三个trainer合并为一个general_trainer
,可以支持多种应用场景。
梳理Trainer逻辑
Trainer的设计是串行的有限状态机,使用processor_register
接口注册各个步骤所调用的函数,依次执行,可在步骤中使用context['status']
指定下一个运行流程。
之前所有流程的实现都在同一个类中, 也只能在同一个类中实现,依赖于self
的成员变量,各个流程的耦合度高,不利于面向对象的灵活开发(如自由组合流程代码,构建一个新的trainer)
因此第二个工作是将各个步骤拆为独立的类,trainer只负责调用各个类,并将上下文词典context
作为信息的载体在各个流程中传递。
基于之前的设计,将trainer的流程拆为了四个大类及两个附属的小类:
大类
-
instance:执行环境的初始化
独立的初衷是为了区分
PS
与Collective
不同的fleet调用,在此处亦可支持用户进行训练前的自由操作,如手动下载数据模型 etc.class PSInstance(InstanceBase): def instance(self, context): from paddle.fluid.incubate.fleet.parameter_server.distribute_transpiler import fleet class CollectiveInstance(InstanceBase): def instance(self, context): from paddle.fluid.incubate.fleet.collective import fleet
-
network:构建program,不同的模式差别极大:
- 如单机可以多阶段,分布式只能一阶段
- 分布式需要调用
distributed_optimizer
-
PS
与collective
的strategy差别较大 -
PS
需要在构建完progrm后根据role执行不同的startup
-
startup:执行参数的初始化
独立的主要目的有很多操作需要在run program前完成,如load参数,手动Set某些变量
-
runner:执行训练
各模式相差较大, 同时也有着
dataset
与dataloader
的区别
小类
- dataset: 将
dataset
与dataloader
的代码单独抽象出来,供runner调用 - terminal: 未在general_trainer中实现,给用户的自留地,方便进行训练结束后的处理,如计算特殊指标,文件的上传,本地的清理等等
预留扩展接口
之前存在的问题
- 修改不便: 如果想修改流程中的某个步骤,比如
SetZero Auc
,在model.py中无法更改,必须在了解Trainer设计的基础上,找到trainer的加载模型的位置,加入set的代码,并且有可能同时需要改三个文件single_train/single_infer/cluster
才能保证单机多机训练及预测的一致性 - 代码冗长: trainer.py过长,过多
if else
对二次开发及debug不友好
基于Trainer中各个流程的抽象,现在用户想要进行训练流程的自定义,变得方便许多。
- 首先:用户只需继承各个流程的基类,从头实现自己的逻辑,或在基本的逻辑上进行修改,即可组成一个完整的trainer
- 其次:用户无需完整的实现所有流程步骤,只需实现自己个性化的步骤
- 最后:用户的代码无需参与paddlerec库的编包,可以指定路径,加载他的代码,参与组网运行,方便用户调试
统一开发风格
我们在添加PaddleRec的模型时,会写以下的代码
from paddlerec.core.model import ModelBase
class Model(ModelBase):
# Build Network
from paddlerec.core.reader import ReaderBase
class TrainReader(ReaderBase):
# Define Reader
并在yaml文件中添加
model: "{workspace}/model.py"
data_converter: "{workspace}/reader.py"
可以总结基本规律
- 继承后缀为
Base
的基类,实现自己的逻辑 - 在yaml文件中添加
py
文件的地址,由paddlerec.core调用
以上流程已被大家接受,并且使用十分方便。因此,Trainer的个性化也使用上述方法进行调用。
首先继承startup
的基类,实现custom_startup
from paddlerec.core.trainers.framework.startup import StartupBase
class Startup(StartupBase):
# Define Startup Process
随后在yaml文件中指定文件位置
startup_class_path: "{workspace}/custom_startup.py"
paddlerec即可调用用户的自定义逻辑,并且无需编包
示例
以TDM为例,实现了个性化的Startup,在训练开始前需要手动Set相关的参数,并且单机多机逻辑不一致。
可以根据context
字典的上下文信息,获取必要的超参。
tdm_startup.py
from paddlerec.core.trainers.framework.startup import StartupBase
class Startup(StartupBase):
def startup(self, context):
logger.info("Run TDM Trainer Startup Pass")
if context["engine"] == EngineMode.SINGLE:
self._single_startup(context)
else:
self._cluster_startup(context)
context['status'] = 'train_pass'
def _single_startup(self, context):
# Single Process
def _cluster_startup(self, context):
# Cluster Process
config.yaml
在runner中指定startup_class
的路径
workspace: "paddlerec.models.treebased.tdm"
runner:
- name: runner1
class: single_train
startup_class_path: "{workspace}/tdm_startup.py"
epochs: 10
device: cpu
Todo
- 文档的更新
- 各模型测试分布式及GPU