Fate flow server使用的flask框架,使用http服务对外提供服务。Fate flow client提交conf和dsl给server,server解析,建模,导出等等操作。
其中dsl(domain-specific-language)是用来描述一个建模任务的,使用json的格式描述了建模过程中不同模块的连接关系。比如
Conf文件也是使用json格式,描述了各个组件参数和资源配置,比如建模参与者及其每方的角色,单机还是集群,后台计算引擎,占用cpu和内存限制等等。比如
pipeline是一个更高级的接口,具有用户好的、容易使用的特点,它将上面提交任务的方式及其很多fate flow client命令进行了封装,使用pipeline建模就像传统方式一样在python里添加需要的模块,定义模块的参数,然后运行,而不用更改dsl的json文件,然后使用命令行提交任务。
这里梳理pipeline是怎样将python代码中的模块和模块的参数解析,提交给fateflow server的。
正文
以简单的pipeline建模展示,我们一般代码如下方式书写,这里省掉了一些具体配置信息
pipeline = PipeLine()
reader_0 = Reader()
dataio_0 = DataIO()
hetero_lr_0 = HeteroLR()
pipeline.add_component(reader_0)
pipeline.add_component(dataio_0)
pipeline.add_component(hetero_lr_0)
pipeline.compile()
job_parameter = JobParameters(backend=Backend.EGGROLL, work_mode=1)
pipeline.fit(job_parameter)
下面逐个步骤解释:
-
首先会定义一个Pipeline的类
该文件在fate/python/fate_client/pipeline/backend/pipeline.py下 -
定义component配置
定义reader_0, dataio_0, hetero_lr_0对象,这些类对应的是fate的算法模块,在fate/python/fate_client/pipeline/component/下,他们并非是真正的算法模块,而是对应的算法模块的参数,在这里定义好参数后,pipeline最终会以某种方式与真正的算法模块(component)联系起来,后面会详细说明。 -
将component配置添加到pipeline
pipeline会使用add_component方法将算法模块添加到seld._components中,self.components是一个字典,key是component的名字,value是component对象 -
compile
compile是使用pipeline的关键,简单说就是将上面用户使用pipeline配置的内容转译为json格式的文件。
fate在联合建模过程中,分train和predict阶段,不同阶段的流程不完全一样。这里以train阶段来解释compile的原理。
compile会调用_construct_train_dsl函数来构建dsl的,生成的结果在self._train_dsl;调用_construct_train_conf函数来构建conf,生成的结果在self._train_conf,此时的train_conf有各个模块的参数,但是还缺少系统资源的信息,这个信息会在fit阶段添加进去。
这两个变量即上面提到的使用fate flow client提交的dsl和conf文件内容。 -
fit阶段
5.1 首先完善conf信息,fit里含有参数job_parameter,该参数会记录运行模式、计算引擎类型、资源配置的信息,这些信息与之前的生成的self._train_conf合并,生成最终的conf文件,依然保存在self._train_conf变量中。这样dsl和conf文件已经完整了。
5.2 调用self._job_invoker.submit_job(self._train_dsl,training_conf),将任务提交。
继续追踪job_infoker
job_invoker是在fate/python/fate_client/pipeline/utils/invoker/job_submitter.py中,调用的是submit_job函数,该函数需要输入dsl和conf,首先,会讲传入的dsl和conf保存到json文件中,然后调用self.client.job.submit(conf_path=submit_path,dsl_path=dsl_path)
继续追踪self.client
self.client=FlowClient(ip=conf.FlowConfig.IP,port=conf.FlowConfig.PORT,version=conf.SERVER_VERSION)。到这里,已经将pipeline提交任务的方式转化成了使用dsl和conf文件的提交方式,最终都是使用了flow client提供的接口,将任务进行提交。
再进一步看flow server是如何接收client提交的任务的。
上面提交任务中调用了FlowClient里的Job.submit方法。
fate/python/fate_client/flow_sdk/client/init.py
//代码占位符
class FlowClient(BaseFlowClient):
job = api.Job()
component = api.Component()
data = api.Data()
queue = api.Queue()
table = api.Table()
task = api.Task()
model = api.Model()
tag = api.Tag()
priviledge = api.Priviledge()
继续追踪Job,在fate/python/fate_client/flow_sdk/client/api/job.py
//代码占位符
def submit(self, conf_path, dsl_path=None):
if not os.path.exists(conf_path):
raise FileNotFoundError('Invalid conf path, file not exists.')
kwargs = locals()
config_data, dsl_data = preprocess(**kwargs)
post_data = {
'job_dsl': dsl_data,
'job_runtime_conf': config_data
}
return self._post(url='job/submit', json=post_data)
可以看到最终调用了post方法,将任务提交给了server,这里的post方法是继承的BaseFlowClient里的方法。继续看一下BaseFlowClient:
fate/python/fate_client/flow_sdk/client/base.py
//代码占位符
class BaseFlowClient:
API_BASE_URL = ''
def __new__(cls, *args, **kwargs):
self = super().__new__(cls)
api_endpoints = inspect.getmembers(self, _is_api_endpoint)
for name, api in api_endpoints:
api_cls = type(api)
api = api_cls(self)
setattr(self, name, api)
return self
def __init__(self, ip, port, version):
self._http = requests.Session()
self.ip = ip
self.port = port
self.version = version
可以看到,里面生成了一个requests的session,传入了ip和port,fate flow client是使用此种方式将任务提交给server的http接口的。