- Notifications
You must be signed in to change notification settings - Fork 5.9k
Description
背景
SOT 是一个与 Python 版本强相关的模块,基本上每个 Python 版本都需要专门适配一次,而每次适配都是一次自底向上从零开始的过程。而跟随这个过程可以帮助新人更快地理解 SOT 整个架构~
在过去,我们最开始是基于 Python 3.8 开发的,之后适配了 Python 3.9 并利用流水线进行了监控(PaddlePaddle/PaddleSOT#105),不久又适配了 Python 3.10(PaddlePaddle/PaddleSOT#112),由于 Python 3.11 内部发生了大量的改动,因此成立了专项对 Python 3.11 进行支持(PaddlePaddle/PaddleSOT#360),如今我们要对 3.12 进行适配了~
适配新版本的最好切入点是了解新版本的改动,比如 Python 3.11,它对整个字节码体系做了大幅的调整,但是调整了什么呢?我们可以在 Release Notes 里找到,但是为什么改动呢?其实我们能从 faster-cpython project 了解到 Python 近几年的加速计划,基本上可以说近几个版本的改动都是为了加速。但是这样改动的目的又是什么,具体实现是什么?这些我们就需要从源码入手来寻找答案了。在 Python 3.11 适配期间,我(@SigureMo)整理了 Python 3.11 核心加速原理——指令特化 并进行了分享,这是 Python 3.11 最大的目标,也是最核心改动的原因,了解这些之后,整个版本适配就没什么悬念了(虽然 3.11 坑确实很多就是了)。
从 Python 3.9-3.11 的适配经验而言,目前看 Python 3.11 > 3.12 > 3.10 > 3.9,这其实也能从 Python 3.12 Release Notes、faster-cpython project 3.13 plan 以及源码找到一些线索,Python 3.12 完成的最大的一件事是使用 DSL 完成了 ceval 的重写,以便实现 Tier 2 优化器,也就是重构,并没有实现 Tier 2 optimizer。Python 3.11 最大的难点就是 Tier 1 实现后整体结构的变动,而 Tier 2 预计会在 3.13 发布,因此 3.12 的适配难度应该低于 3.11。(但也可预见未来 Python 3.13 适配应该难于 3.11)
另一方面,了解 SOT 架构也是对新版本适配有帮助的,PEP 523 是 SOT JIT 编译的基础,我们该模块源码放在 paddle/fluid/pybind/eval_frame.c,每次适配新版本的开始都是这个最底层的模块。
更上一层地,是模拟执行核心模块 python/paddle/jit/sot/opcode_translator/executor/opcode_executor.py 和字节码生成模块 python/paddle/jit/sot/opcode_translator/executor/pycode_generator.py。在模拟执行模块中,我们需要对每条字节码进行模拟执行,并修改模拟执行器的状态,在字节码生成模块中,我们需要根据模拟执行的结果来生成新的字节码,这些都是和版本强相关的模块(主要是字节码的设计)。
此外大多是版本无关的代码,一般不需要进行适配。
Eval Frame 适配
- eval frame 基本结构适配 [SOT][3.12] Eval frame compat for Python 3.12 #61272
- eval frame 申请、释放逻辑修复 [SOT][3.12] Fix that
framein eval custom code was not released intstate#61703
字节码适配
字节码适配任务即对新版本新增、修改的字节码进行适配,根据 Python 3.12 字节码变更 和 Python 各版本字节码差异表格,变动和新增字节码如下:
行为变动
| 序号 | 字节码名称 | 认领人 | PR |
|---|---|---|---|
| ✅1 | LOAD_ATTR | @gouzil | #61305 |
新增
| 序号 | 字节码名称 | 认领人 | PR |
|---|---|---|---|
| ✅1 | BINARY_SLICE | @diadestiny | #62028 |
| ✅2 | STORE_SLICE | @diadestiny | #62028 |
| ✅3 | CALL_INTRINSIC_1 | @gouzil | #61995 |
| 4 | CALL_INTRINSIC_2 | @gouzil | |
| 5 | CLEANUP_THROW | ||
| 6 | END_SEND | ||
| 7 | LOAD_FAST_AND_CLEAR | ||
| ✅8 | LOAD_FAST_CHECK | @gouzil | #62218 |
| 9 | LOAD_FROM_DICT_OR_DEREF | ||
| 10 | LOAD_FROM_DICT_OR_GLOBALS | ||
| 11 | LOAD_LOCALS | ||
| ✅12 | LOAD_SUPER_ATTR | @SigureMo | #61858 |
| ✅13 | RETURN_CONST | @diadestiny | #61964、#62073 |
| 14 | SETUP_CLEANUP | ||
| 15 | JUMPPseudo-instructions | ||
| 16 | JUMP_NO_INTERRUPTPseudo-instructions | ||
| ✅17 | END_FOR | @diadestiny | #62008 |
Note
并不是每个新增 / 变动的字节码都需要适配,这里只是列出全部,有些字节码是一些复杂特性才会出现的(比如异常、异步、match 语法)此种情况不需要适配,Python 3.12 为了实现 PEP 695 也有一些新的字节码,这些字节码我们也是现阶段不需要支持的,对 PEP 695 实现感兴趣可以参考 Jelle 的博客
字节码的适配,即字节码模拟执行逻辑的适配,也就是修改 OpcodeExecutor,字节码的模拟执行逻辑可参考以下内容:
- 字节码文档 https://docs.python.org/3.12/library/dis.html
- 源码
- 3.11 及之前可参考
ceval.c - 3.12 及之后可参考使用 DSL 编写的
bytecode.c以及根据其生成的generated_cases.c.h
- 3.11 及之前可参考
建议每个实现都看看源码,文档中有很多细节是不会暴露给用户的,但我们模拟执行是需要的
其它模块适配
TODO...
单测汇总见: #61174