环境
- Python 3.12
- expr-codegen 0.16.6
- polars-ta 0.5.17
- Polars 1.35.2
- Linux x86_64
- Polars thread pool: 20
问题描述
同一个目标表达式单独传给 ExprTool.all,与它和另一个表达式一起传入时,目标列结果不同。表达式和输入行没有变化,skip_simplify=True,regroup=False。
独立复现脚本见附件 expr_codegen_issue_repro.py。
运行:
python expr_codegen_issue_repro.py
输出:
{'polars': '1.35.2', 'expr_codegen': '0.16.6', 'polars_ta': '0.5.17', 'threads': 20}
{'null_mismatch': 0, 'value_mismatch': 10919, 'max_abs_diff': 1.5013887474838818}
改为单线程:
POLARS_MAX_THREADS=1 python expr_codegen_issue_repro.py
输出完全一致:
{'polars': '1.35.2', 'expr_codegen': '0.16.6', 'polars_ta': '0.5.17', 'threads': 1}
{'null_mismatch': 0, 'value_mismatch': 0, 'max_abs_diff': 0.0}
初步定位
我检查了生成的目标 DAG,未发现 SymPy CSE 把目标表达式代数改写错误。逐级比较中间列后发现:
- 目标的前两级中间量逐值完全一致。
- 第二个
cs_zscore 首次出现差异:175 个值不同,最大绝对差 2.220446049250313e-16。
- 后续 3 日
ts_corr 和 3 日 ts_zscore 将该差异放大,最终最大绝对差达到 1.5013887474838818。
POLARS_MAX_THREADS=1 后所有差异消失。
从生成代码看,批量表达式会改变公共子表达式的物化方式、DAG 层级、with_columns 分组和 TS/CS 排序顺序。当前 CS 阶段生成 df.sort(_DATE_),同日证券没有 _ASSET_ 决键;此外,多列横截面归约的执行布局会随批次变化。二者都会使并行 mean/std 的浮点归约顺序变化。
因此,这看起来不是“CSE 生成了错误公式”,而是跨输出 CSE/DAG 编排没有保持批量与单独执行的数值一致性。低方差短窗口相关会把机器精度级差异放大成实质差异。
我也测试了 regroup=True:它能让本复现中的两表达式批次一致,但在更大的表达式批次中仍会出现同样漂移,因此不能作为普遍修复。源码中的 regroup 参数说明目前也标注为“不稳定”。
期望行为
同一输入、同一目标表达式和同一参数下,增加无关输出不应显著改变目标结果。至少希望提供以下一种能力:
- 可选的输出隔离模式:只在单个最终输出内部做 CSE,禁止跨最终输出改变物化边界;或
- 可选的确定性代码生成模式,并保证单独与批量执行在明确容差内一致;或
- 明确记录该限制,并提供推荐的安全批量方式。
建议增加回归测试:包含 CS -> TS -> CS -> TS 链、低方差窗口、动态 null mask,并比较目标表达式单独与批量执行结果。
补充
把 CS 排序临时改为 df.sort(_DATE_, _ASSET_) 能修复较小批次,但不能修复包含多个并行横截面归约的较大批次;因此稳定排序是改进项,但不是完整修复。
expr_codegen_issue_repro.py
环境
问题描述
同一个目标表达式单独传给
ExprTool.all,与它和另一个表达式一起传入时,目标列结果不同。表达式和输入行没有变化,skip_simplify=True,regroup=False。独立复现脚本见附件
expr_codegen_issue_repro.py。运行:
输出:
改为单线程:
输出完全一致:
初步定位
我检查了生成的目标 DAG,未发现 SymPy CSE 把目标表达式代数改写错误。逐级比较中间列后发现:
cs_zscore首次出现差异:175 个值不同,最大绝对差2.220446049250313e-16。ts_corr和 3 日ts_zscore将该差异放大,最终最大绝对差达到1.5013887474838818。POLARS_MAX_THREADS=1后所有差异消失。从生成代码看,批量表达式会改变公共子表达式的物化方式、DAG 层级、
with_columns分组和 TS/CS 排序顺序。当前 CS 阶段生成df.sort(_DATE_),同日证券没有_ASSET_决键;此外,多列横截面归约的执行布局会随批次变化。二者都会使并行mean/std的浮点归约顺序变化。因此,这看起来不是“CSE 生成了错误公式”,而是跨输出 CSE/DAG 编排没有保持批量与单独执行的数值一致性。低方差短窗口相关会把机器精度级差异放大成实质差异。
我也测试了
regroup=True:它能让本复现中的两表达式批次一致,但在更大的表达式批次中仍会出现同样漂移,因此不能作为普遍修复。源码中的regroup参数说明目前也标注为“不稳定”。期望行为
同一输入、同一目标表达式和同一参数下,增加无关输出不应显著改变目标结果。至少希望提供以下一种能力:
建议增加回归测试:包含
CS -> TS -> CS -> TS链、低方差窗口、动态 null mask,并比较目标表达式单独与批量执行结果。补充
把 CS 排序临时改为
df.sort(_DATE_, _ASSET_)能修复较小批次,但不能修复包含多个并行横截面归约的较大批次;因此稳定排序是改进项,但不是完整修复。expr_codegen_issue_repro.py