Skip to content
Yi Wang edited this page Apr 26, 2020 · 1 revision

This is a port of the notes of @shendiaomo

一、主要参考资料

SQLFlow重构初步规划

https://github.com/sql-machine-learning/sqlflow/issues/1434

目录组织

顶层package分为几部分

  • parser
  • ir
  • resolver
  • verifier
  • codegen
  • executor

一条语句过来,按顺序从上至下经过这四个目录下的模块

流程组织

从执行流程来看,每个package提供一个到两个主要的函数:

  • parser.Parse
  • resolver.ResolveProgram
  • verifier.Verify
  • workflow.GenCode
  • workflow.Run

一条语句过来,按顺序从上到下执行一系列函数调用

未解决问题

以上讨论集中在big picture,一些更细节的内容尚未涉及

  1. 尚未涉及sqlflow_submitter这个Python package
  2. 此package和Python API的关系?
  3. 尚未涉及如何使支持新的引擎(如PyTorch)、新的平台(如PAI)更加自然
  4. 目前PAI部分的codegen和TensorFlow的codegen之间的关系较为复杂
  5. 尚未涉及diagnostics在系统中的位置
  6. attribute check作为resolver/verifier的一部分?
  7. 仿照其它语言的异常机制,设计一个Error体系?
  8. Python部分的错误如何在Golang中更好地报告
  9. 尚未涉及如何定义sqlflow_models和Model Zoo的规范,使用户自定义模型能更好融入到原有框架中

MySQL源码组织

作为老牌开源DBMS,有必要看看MySQL是否有我们值得借鉴之处,特别是在数据库概念的组织上。 MySQL提供了一份源码阅读指南。有趣的是,MySQL也有个sql目录,看来由于出现过早,有些问题并未妥善解决https://dev.mysql.com/doc/internals/en/sql-directory.html 大部分流程MySQL都放在了sql/sql_parse.cc这个文件中,该文件约有7000行,这个文件parse SQL后,做一些基本检查,然后分发到其余文件,如SELECT语句会调用sql/sql_select.cc。这样看起来也比较清晰,后续我们如果支持了更多语句的话,也可以参考。 MySQL的诊断信息并未独立成模块,而是分散在各处,通过my_error函数作为报错的统一出口,同时错误会归类到mysqld_error.h等头文件中。也有部分是用C++ exception解决。总的来说这部分值得借鉴的主要是MySQL为各种error归类的方式。 TODO: 由于MySQL代码较多,并未深入其细节。后续补充必要内容。 _

clang源码组织

clang作为编译器中的后起之秀,代码组织相对于MySQL清晰了很多:https://github.com/llvm-mirror/clang/tree/master/lib,这和我们初步规划的内容更接近。 clang的核心是按lib来组织各个模块,和SQLFlow类似的目录包括

  • Lex
  • Parse
  • AST
  • Sema
  • CodeGen

值得称道的是,clang虽然没有把Diagnostics放到一个单独的lib中,但在Basic这个lib下有非常多diagnostics相关的代码。可见其将这部分当作一个重要的事情来做,clang刚出来的时候能从gcc分到一杯羹也有一个重要的原因是诊断信息更加易读。当然,SQL语言本身比C++简单得多,我们不需要做出如此多的工作,但可能确实可以借鉴clang,将所有诊断信息都汇总起来。 TODO: 由于clang代码较多,并未深入其细节。后续补充必要内容。 _

二、后续方向设想

这部分是接下来工作初步的设想,还缺少很多细节,主要来自直觉和本文第一部分,接下来一段时间会不断深化思考,进行调整,最终的方案和当下版本可能会有区别。

语义检查

将第一部分提及的Resolve、Verify,和第一部分未提及的codegen/attribute/都放到一个顶级目录下,参考clang,取名叫sema,这部分的目标是将尽量多的检查前置,使问题在运行的早期尽快暴露出来。

  • 这部分工作量会集中在「在Golang中为sqlflow_submitter中的Python代码增加语义检查」上。
  • 这部分工作虽然叫sema,但并非只是static type checking,而是包含可前置检查的所有语义

诊断系统diagnostics

在顶级目录下新增这一目录,参考clang和MySQL,将所有可能出现的错误统一整理,提供统一出口,diagnostics的作用更多是作为lib供其他所有模块(如sema)调用。

  • 这部分工作需要整理目前所有可以前置检查的错误类型(如语义检查),规避"ERROR: runSQLProgram error: unexpected type on attribute train.epoch. expect int, received a(string)"这样的报错方式。统一为:“Error 1046: unexpected type on attribute 'train.epoch': expect int, received 'a'(string)”等。
  • 这部分工作需要解决Python报出运行时错误的问题,不再打印出一大串Python源码来惊吓用户。目前如果输入「SELECT * FROM iris.train TO TRAIN DNNClassifier WITH model.hidden_units=123 LABEL class INTO gogogo;」用户将看到约200行的错误提示,错误提示的末尾是「TypeError: 'int' object is not iterable」,当然,这个问题需要通过上文提到的「在Golang中为sqlflow_submitter中的Python代码增加语义检查」,但也会有无法在语义检查阶段处理的运行时错误,我们只告诉用户“Error 1046: training failed: TypeError: 'int' object is not iterable”即可
  • 这部分工作需要提供错误列表手册,用户在遇到棘手问题时可以通过查询编号自行解决
  • 这部分工作需要提供解决我们自身debug的问题,错误信息对应的stacktrace应当记录在后台日志系统,方便定位
  • 这部分工作需要汇总PAI、DataWorks、MySQL、MaxCompute等各种日志和错误,归纳分类

codegen

该目录需要

  1. 理清PAI TF和TensorFlow的关系,予以更合理的组织方式
  2. 同时,理清「平台」和「引擎」之间的关系
  3. COLUMN子句从TensorFlow codegen中移出,理清COLUMN子句的定位
  4. 提供统一的模型存储格式(nice to have)
  5. PMML、SavedModel、RTP...
  6. 抽象添加新引擎所需工作,提供相应指导文档
  7. 参考Golang image模块,采用注册机制?
  8. 对引擎予以合理的封装,对上提供统一的接口,例如:
type Function struct {
    Run()  // SELECT ... TO RUN ...
}
type Engine interface {
    SemaCheck()  // Call package `sema', called by Train/Predict/Explain/Evaluate
    Transform()
    Train()
    Predict()
    Explain()
    Evaluate()
}

sqlflow_submitter

该目录可认为是SQLFlow这门「编程语言」的runtime,如libstdc++.so之于gcc,需要在重构工作中精心重铸:

  1. 理清和codegen的关系
  2. 将golang中可能移入的代码尽量移入?
  3. 提供统一接口,如:
# 如果此处能封装好,则golang的codegen就有望不需要再封装,所需工作将大大减轻
# 代码结构也会更清晰
class Engine():
    @abstractmethod
    def Transform():
    	pass
    @abstractmethod
    def Train():
        pass
    @abstractmethod
    def Predict():
        pass
    @abstractmethod
    def Explain():
        pass
    @abstractmethod
    def Evaluate():
        pass
  1. 以sqlflow_submitter为基础,提供Python API(倘若如此,sqlflow_submitter可改名为sqlflow了?)
  2. 从以下代码可以看出,对xgboost和tensorflow而言,通过Python调用sqlflow_submitter的语法和我们之前讨论过的sqlflow Python API的写法颇为类似:
# pkg/sql/codegen/xgboost/template_train.go
train(datasource='''{{.DataSource}}''',
      select='''{{.TrainSelect}}''',
      model_params=model_params,
      train_params=train_params,
      feature_metas=feature_metas,
      feature_column_names=feature_column_names,
      label_meta=label_meta,
      validation_select='''{{.ValidationSelect}}''',
      disk_cache="{{.DiskCache}}" == "true",
      batch_size={{.BatchSize}},
      epoch={{.Epoch}},
      is_pai="{{.IsPAI}}" == "true",
      pai_train_table="{{.PAITrainTable}}",
      pai_validate_table="{{.PAIValidateTable}}")

# pkg/sql/codegen/tensorflow/template_train.go
train(datasource="{{.DataSource}}",
      estimator={{.Estimator}},
      select="""{{.TrainSelect}}""",
      validation_select="""{{.ValidationSelect}}""",
      feature_columns=feature_columns,
      feature_column_names=feature_column_names,
      feature_metas=feature_metas,
      label_meta=label_meta,
      model_params=model_params_constructed,
      validation_metrics="{{index .ValidationParams "metrics"}}".split(","),
      save="{{.Save}}",
      batch_size={{index .TrainParams "batch_size" | attrToPythonValue}},
      epoch={{index .TrainParams "epoch" | attrToPythonValue}},
      validation_steps={{index .ValidationParams "steps" | attrToPythonValue}},
      verbose={{index .TrainParams "verbose" | attrToPythonValue}},
      max_steps=train_max_steps,
      validation_start_delay_secs={{index .ValidationParams "start_delay_secs" | attrToPytho
nValue}},
      validation_throttle_secs={{index .ValidationParams "throttle_secs" | attrToPythonValue
}},
      save_checkpoints_steps={{index .TrainParams "save_checkpoints_steps" | attrToPythonVal
ue}},
      log_every_n_iter={{index .TrainParams "log_every_n_iter" | attrToPythonValue}},
      is_pai="{{.IsPAI}}" == "true",
      pai_table="{{.PAITrainTable}}",
      pai_val_table="{{.PAIValidateTable}}")

server和proto

server和proto都是pkg下现存的目录之一,这块在设想中唯一需要做的工作是将HDFS相关的session移出:

message Session {
    string token = 1;
    string db_conn_str = 2;
    bool exit_on_submit = 3;
    string user_id = 4;
    // for loading CSV to hive
    string hive_location = 5;
    string hdfs_namenode_addr = 6;
    string hdfs_user = 7;
    string hdfs_pass = 8;
    string submitter = 9;
}

不管怎么说,HDFS和Hive相关的配置可能确实需要放到一个更合适的位置。

小结

设想中pkg目录的组织:

  • parser
    • lexer
    • parser
  • ir
  • sema
    • resolver
    • verifier
    • attribute
  • diagnostics
  • codegen
  • executor

TODO: 这部分写得很简略,待其余部分完善后补充细节。