Open
Description
在 #2158 (comment), #2158 (comment), #2158 (comment), #2158 (comment) 中,我们提到了codegen重构的方案及其与Python API的关系,在#2123 中,我们讨论了Python API。现将这个话题集中到这个issue中讨论。
要点记录如下:
基于sqlflow_submitter来实现#2123中的Python API
#2123 提到两个Python API,分别对应developer SDK和client,这里只讨论第一个API,即:
import sqlflow
import keras
class MyClassifierModel(keras.Model):
def __init__(self, n_classes=3):
...
sqlflow.init(datasource='mysql://(root:root)127.0.0.1:3306/iris')
sqlflow.train(
'SELECT * FROM tbl',
model_def=lambda: MyClassifierModel(n_classes=3),
params={'epochs': 10}, into='my_model',
columns={'color': sqlflow.columns.categorical(['RED', 'BLUE', 'YELLOW'])})
在此基础上简化codegen,调用Python API实现codegen的原有逻辑
原有codegen
codegen
|- tensorflow
|- codegen.go
|- template_train.go
|- xgboost
|- codegen.go
|- template_train.go
|- pai
|- codegen.go
|- template_train.go
其中codegen所生成的代码将由golang启动一个远程或本地的python进程来执行。
目前架构的问题是
- codegen.go和template.go中充斥着重复的结构和以字符串形式存在的python代码
- codegen.go中有大量用于go->python类型转换的代码
- 平台(pai)和引擎(tensorflow/xgboost)的代码没有清晰的结构划分,隔离较差
- 为减少无谓的type switch,在go中用visitor模式实现引擎和SQL语句的double dispatch,难懂难改
- 新语法、新功能往往要跨多个package/struct修改代码,隔离较差
- 使反直觉逻辑成为常态
- 建表的字段只有在python代码中才能得知,但却只能在go中执行
- 用go对应python的语法特性,这使在当下codegen的基础上封装python API几乎不可能在现有代码上封装python API,在现在的代码基础上封装Python API,调用栈如下:
这样带来的问题是anti-pythonic,试举一二:
python -> golang -> python user | contributor & developer
- error handling:如果API建立在go上,则需要将大量python exception映射为golang Error,再映射为python exception
- docstring:以TO TRAIN为例,由于隔了中间golang这层,用户无法简单地通过
help()
查看一个训练器如何使用
简化之后的codegen
codegen
|- tf_type_checking.go
|- xgb_type_checking.go
|- pai_random_forest_type_checking.go
|- ...
|- codegen.go
codegen.go需要做的事情只是
func Train(p Parameters) {
/*将p映射为Python对象,启动远程或本地进程调用Python API:
sqlflow.init(datasource='p.DBConnStr')
sqlflow.train(
select=p.OriginalSQL,
model_def=p.EstimatorCls(**p.ModelParams),
platform=p..EnumPlatform,
columns=p.Columns,
into=p.Into)
}
*/
// 其余Predict/Run之类的实现以此类推
新架构将解决哪些问题
- codegen.go和template.go中充斥着重复的结构和以字符串形式存在的python代码
只需要一个codegen.go,不再有重复代码
- codegen.go中有大量用于go->python类型转换的代码
类型转换仍然需要,但将会在同一个package中作为util,而不是分散到各个package,结构更清晰
- 平台(pai)和引擎(tensorflow/xgboost)的代码没有清晰的结构划分,隔离较差
- 为减少无谓的type switch,在go中用visitor模式实现引擎和SQL语句的double dispatch,难懂难改
- 新语法、新功能往往要跨多个package/struct修改代码,隔离较差
对以上两条,Python唯一的优势是语法表达能力更强,但仍然需要double dispatch机制
- 使反直觉逻辑成为常态 - 建表的字段只有在python代码中才能得知,但却只能在go中执行
所有逻辑都在Python中解决,所有流程都会更符合直觉
- 用go对应python的语法特性,这使在当下codegen的基础上封装python API几乎不可能在现有代码上封装python API,在现在的代码基础上封装Python API,调用栈如下:
这样带来的问题是anti-pythonic,试举一二:python -> golang -> python user | contributor & developer
- error handling:如果API建立在go上,则需要将大量python exception映射为golang Error,再映射为python exception
- docstring:以TO TRAIN为例,由于隔了中间golang这层,用户无法简单地通过
help()
查看一个训练器如何使用
封装出自然的Python API,这是新架构的最大优势
该设计的实质
- The golang codebase degrades to a shell around the python kernel. That shell is composed of the SQL syntax sugar and a gRPC service.
该设计的好处
- 训练相关的代码几乎可以不依赖golang来开发,更加自然,能减轻codegen中大量的检查和转换代码,整体开发和维护成本会降低
- 依赖Python3.8新增的static type checking,可以进一步解决动态语言在维护成本上的问题
- 大部分database的driver可以不需再行维护了
- 提供一个更自然的Python API
golang -> python
is much more natural thanpython -> golang -> python
- diagnostics可以部分放到Python中支持,可以原生地处理各种Python异常
- 在全面支持Python3.6以后,甚至type checking也可以放到Python中去做
该设计的问题
- 具有一定工作量
- feature derivation需要迁往Python
- 在支持couler时曾有过讨论和尝试 @typhoonzero
- codegen/tensorflow中的COLUMN实现需要迁往Python
- @brightcoder01 @typhoonzero 可能需要关注
- feature derivation需要迁往Python
- 更多核心代码移入python,使用动态语言会稍微增加一些开发成本
- 部分技术细节有一定的实现难度
- 对应gomaxcompute/goalisa的pymaxcompute/pyalisa @weiguoz @brightcoder01
小结
总体来说,这个方案可以更优雅地解决目前的几个问题:
- Python API
- 简化代码中各种为支持Golang到Python的转换引入的代码
- 简化diagnostics的设计
个人感觉,瑕不掩瑜,是个值得花时间讨论出更多细节的方向。