Skip to content

Refactory Discussion 1: code generator and Python API #2168

Open
@shendiaomo

Description

@shendiaomo

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

目前架构的问题是

  1. codegen.go和template.go中充斥着重复的结构和以字符串形式存在的python代码
  2. codegen.go中有大量用于go->python类型转换的代码
  3. 平台(pai)和引擎(tensorflow/xgboost)的代码没有清晰的结构划分,隔离较差
    • 为减少无谓的type switch,在go中用visitor模式实现引擎和SQL语句的double dispatch,难懂难改
    • 新语法、新功能往往要跨多个package/struct修改代码,隔离较差
    • 使反直觉逻辑成为常态
      • 建表的字段只有在python代码中才能得知,但却只能在go中执行
  4. 用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需要做的事情只是

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
    • codegen/tensorflow中的COLUMN实现需要迁往Python
  2. 更多核心代码移入python,使用动态语言会稍微增加一些开发成本
  3. 部分技术细节有一定的实现难度

小结

总体来说,这个方案可以更优雅地解决目前的几个问题:

  1. Python API
  2. 简化代码中各种为支持Golang到Python的转换引入的代码
  3. 简化diagnostics的设计

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

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions