weixin_39964819
weixin_39964819
2021-01-10 13:58

Refactory Discussion 1: code generator and Python API

在 https://github.com/sql-machine-learning/sqlflow/pull/2158#discussion_r415757386, https://github.com/sql-machine-learning/sqlflow/pull/2158#discussion_r415664863, https://github.com/sql-machine-learning/sqlflow/pull/2158#discussion_r416205353, https://github.com/sql-machine-learning/sqlflow/pull/2158#discussion_r415782428 中,我们提到了codegen重构的方案及其与Python API的关系,在https://github.com/sql-machine-learning/sqlflow/issues/2123 中,我们讨论了Python API。现将这个话题集中到这个issue中讨论。

要点记录如下:

基于sqlflow_submitter来实现#2123中的Python API

2123 提到两个Python API,分别对应developer SDK和client,这里只讨论第一个API,即:

python
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进程来执行。

目前架构的问题是 1. codegen.go和template.go中充斥着重复的结构和以字符串形式存在的python代码 1. codegen.go中有大量用于go->python类型转换的代码 1. 平台(pai)和引擎(tensorflow/xgboost)的代码没有清晰的结构划分,隔离较差 - 为减少无谓的type switch,在go中用visitor模式实现引擎和SQL语句的double dispatch,难懂难改 - 新语法、新功能往往要跨多个package/struct修改代码,隔离较差 - 使反直觉逻辑成为常态 - 建表的字段只有在python代码中才能得知,但却只能在go中执行 1. 用go对应python的语法特性,这使在当下codegen的基础上封装python API几乎不可能在现有代码上封装python API,在现在的代码基础上封装Python API,调用栈如下:


    python -> golang -> python
     user        |    contributor & developer
    
这样带来的问题是anti-pythonic,试举一二: - 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需要做的事情只是

golang
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之类的实现以此类推

新架构将解决哪些问题

  1. codegen.go和template.go中充斥着重复的结构和以字符串形式存在的python代码

只需要一个codegen.go,不再有重复代码

  1. codegen.go中有大量用于go->python类型转换的代码

类型转换仍然需要,但将会在同一个package中作为util,而不是分散到各个package,结构更清晰

  1. 平台(pai)和引擎(tensorflow/xgboost)的代码没有清晰的结构划分,隔离较差
    • 为减少无谓的type switch,在go中用visitor模式实现引擎和SQL语句的double dispatch,难懂难改
    • 新语法、新功能往往要跨多个package/struct修改代码,隔离较差

对以上两条,Python唯一的优势是语法表达能力更强,但仍然需要double dispatch机制

- 使反直觉逻辑成为常态
    - 建表的字段只有在python代码中才能得知,但却只能在go中执行

所有逻辑都在Python中解决,所有流程都会更符合直觉

  1. 用go对应python的语法特性,这使在当下codegen的基础上封装python API几乎不可能在现有代码上封装python API,在现在的代码基础上封装Python API,调用栈如下:
    
        python -> golang -> python
         user        |    contributor & developer
        
    这样带来的问题是anti-pythonic,试举一二:
    • 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.

该设计的好处

  1. 训练相关的代码几乎可以不依赖golang来开发,更加自然,能减轻codegen中大量的检查和转换代码,整体开发和维护成本会降低
  2. 依赖Python3.8新增的static type checking,可以进一步解决动态语言在维护成本上的问题
  3. 大部分database的driver可以不需再行维护了
  4. 提供一个更自然的Python API
    • golang -> python is much more natural than python -> golang -> python
  5. diagnostics可以部分放到Python中支持,可以原生地处理各种Python异常
  6. 在全面支持Python3.6以后,甚至type checking也可以放到Python中去做

该设计的问题

  1. 具有一定工作量
    • feature derivation需要迁往Python
      • 在支持couler时曾有过讨论和尝试
    • codegen/tensorflow中的COLUMN实现需要迁往Python
      • 可能需要关注
  2. 更多核心代码移入python,使用动态语言会稍微增加一些开发成本
  3. 部分技术细节有一定的实现难度
    • 对应gomaxcompute/goalisa的pymaxcompute/pyalisa

小结

总体来说,这个方案可以更优雅地解决目前的几个问题: 1. Python API 1. 简化代码中各种为支持Golang到Python的转换引入的代码 - https://github.com/sql-machine-learning/sqlflow/blob/develop/pkg/sql/codegen/tensorflow/codegen.go#L357-L381 - https://github.com/sql-machine-learning/sqlflow/blob/develop/pkg/ir/feature_column.go - Executor/SQLStatement实现的double dispatch 1. 简化diagnostics的设计

个人感觉,瑕不掩瑜,是个值得花时间讨论出更多细节的方向。

该提问来源于开源项目:sql-machine-learning/sqlflow

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

11条回答

  • weixin_39964819 weixin_39964819 3月前

    赞同将目前 code generate 的代码迁移到 python 中,这样模块可以更好地解耦,代码也更清晰,同时 python 模块可以独立出来,即使没有 go 的部分也可以使用。

    同时有几个疑问:

    1. 能否不要 code generate 这个概念呢?直接将 python 部分作为一个系统模块(可能包含现有的 submitter、executor),类似于 parser 模块一样,我们只用给这个模块传参,然后获取调用结果,再叫做 code generate 就不太合适了。
    2. 上面提到还需要进行类型转换,感觉不是很需要,用 pb 或 json 序列化参数传过去是不是就好了?

    我说的类型转换就是指json或pb这个过程哈

    点赞 评论 复制链接分享
  • weixin_39644614 weixin_39644614 3月前
    python
    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'])})
    

    May not deal with the case when we calling PAI ML components like randomforest and kmeans, there is no class or function to call to generate a PAI ML component object.

    We should be able to support all model_def parameters:

    1. Pre-made estimators
    2. Keras functional models
    3. Keras sub-class models
    4. PAI ML components
    5. XGboost models

    and training platforms like:

    1. local
    2. PAI
    3. Kubernetes
    4. ElasticDL
    点赞 评论 复制链接分享
  • weixin_39614831 weixin_39614831 3月前

    我理解是类似 sqlflow.train(..., mode=argo) 这种接口

    现在 codegen 生成的是 Couler 程序,每个 step 执行的是一段原始的 SQL:

     python
    sqlflow.step(sql='SELECT ...', end=..., image=...)
    

    甚至可以用 High-Level API 描述 workflow,例如:

     python
    
    sqlflow.init(model=workflow, datasource='')
    
    sqlflow.mysql.query('SELECT ....')
    sqlfllow.train('SELECT ... TO TRAIN')
    sqlflow.predcit('SELECT ... TO PREDICT...')
    
    点赞 评论 复制链接分享
  • weixin_39964819 weixin_39964819 3月前

    该设计的实质

    • The golang codebase degrades to a shell around the python kernel. That shell is composed of the SQL syntax sugar and a gRPC service.

    补充三个角度:

    1. 第一个角度,golang目前完成的工作都可以用python代替,而python完成的工作golang代替不了,是不是可以认为python代码本来就是核心?而我们要做的是将更适合在kernel完成的工作移入kernel

    2. 第二个角度,如果抛开SQLFlow完成的工作(AI),单从三种语言切换的角度来看,golang代码现在在做的应该是这么几件事:

      1. 把SQL语言的语法结构映射成golang的数据结构(即IR)
      2. 把golang的数据结构映射成python的数据结构(type映射、column映射等)
      3. 调用python代码

      所以,把更多AI逻辑移入python,使golang更专注于这三件事,也是比较合理的

    3. 第三个角度是站在反面立场来看,这个issue有些地方太过简略,虽然看似自圆其说,但到底能简化多少代码,有哪些具体的工作能从中受益,并没有能给出详细的说明

    点赞 评论 复制链接分享
  • weixin_39644614 weixin_39644614 3月前

    Agree with move most of the code generation job to Python, I list works must be done to implement this design:

    1. Python API takes the SQLFlow IR struct and generates code.
    2. Read database tables using Python drivers to do feature derivation and verifying.
    3. Re-implement feature derivation using Python.
    4. Create predict, explain and evaluate result table using Python database APIs, including:
      1. Create MySQL, Hive tables in Python, this is simple.
      2. Create MaxCompute tables when training on PAI: may not have any API for doing this.
      3. Create MaxCompute tables by calling alisa: need a Python implementation of goalisa.
    5. Call OSS API to determine the model type before predicting.
    6. Deal with explain's output pictures.
    点赞 评论 复制链接分享
  • weixin_39583655 weixin_39583655 3月前

    'SELECT * FROM table TO TRAIN' 将会翻译成一个Workflow,以下两部分: - Workflow的整体链路(例如包含多少个Step,不同Step间的依赖关系) - 每个Workflow Step需要完成的任务

    这部分code_gen会在go中完成,还是go只负责parse成IR,交给Python来生成上述两项信息?

    点赞 评论 复制链接分享
  • weixin_39964819 weixin_39964819 3月前

    'SELECT * FROM table TO TRAIN' 将会翻译成一个Workflow,以下两部分:

    • Workflow的整体链路(例如包含多少个Step,不同Step间的依赖关系)
    • 每个Workflow Step需要完成的任务

    这部分code_gen会在go中完成,还是go只负责parse成IR,交给Python来生成上述两项信息?

    👍这个问题我还没有完全想清楚,所以没写。初步想法是都在python中完成,这样也更容易和couler对接。也请 兄批判。

    点赞 评论 复制链接分享
  • weixin_39614831 weixin_39614831 3月前

    Following the comment: https://github.com/sql-machine-learning/sqlflow/issues/2168#issuecomment-620990623

    I tried to list what in the Go code: - SQLFlow gRPC server to serve gRPC request - The collective parser - Verifying data schema/ model schema/ attribute - Codegen with little code.

    From https://github.com/sql-machine-learning/sqlflow/issues/2169 , It's hard to diagnose errors from the generated code runtime, and most of the codebase is in Python, maybe we can re-implement it in Python?

    If we move Verifying to Python, we don't need to maintain two gohive/pyhive database driver package.

    点赞 评论 复制链接分享
  • weixin_39614831 weixin_39614831 3月前

    'SELECT * FROM table TO TRAIN' 将会翻译成一个Workflow,以下两部分: Workflow的整体链路(例如包含多少个Step,不同Step间的依赖关系) 每个Workflow Step需要完成的任务 这部分code_gen会在go中完成,还是go只负责parse成IR,交给Python来生成上述两项信息?

    交给 Python 是说Python提供类似:

     python
    sqlflow.workflow([ir1, ir2, ir3...])
    

    这样的接口吗?

    点赞 评论 复制链接分享
  • weixin_39964819 weixin_39964819 3月前

    'SELECT * FROM table TO TRAIN' 将会翻译成一个Workflow,以下两部分: Workflow的整体链路(例如包含多少个Step,不同Step间的依赖关系) 每个Workflow Step需要完成的任务 这部分code_gen会在go中完成,还是go只负责parse成IR,交给Python来生成上述两项信息?

    交给 Python 是说Python提供类似:

    python
    sqlflow.workflow([ir1, ir2, ir3...])
    

    这样的接口吗?

    我理解是类似

    python
    sqlflow.train(..., mode=argo)
    

    这种接口

    点赞 评论 复制链接分享
  • weixin_39627361 weixin_39627361 3月前

    赞同将目前 code generate 的代码迁移到 python 中,这样模块可以更好地解耦,代码也更清晰,同时 python 模块可以独立出来,即使没有 go 的部分也可以使用。

    同时有几个疑问: 1. 能否不要 code generate 这个概念呢?直接将 python 部分作为一个系统模块(可能包含现有的 submitter、executor),类似于 parser 模块一样,我们只用给这个模块传参,然后获取调用结果,再叫做 code generate 就不太合适了。 2. 上面提到还需要进行类型转换,感觉不是很需要,用 pb 或 json 序列化参数传过去是不是就好了?

    点赞 评论 复制链接分享