<aside>
AI 主导编写,请自行辨别信息
</aside>
simulator_v2 是 aiak_ds_tool 库中的推理性能模拟器,给定模型配置和运行参数,通过查询预采集的算子性能数据库,输出逐层/逐算子的耗时预估。
核心调用链:
SimulatorV2App.export_result()
└─ SimulatorEngine.simulate() # 对每个 case 逐层模拟
└─ OperatorRegistry.get_time() # 对每个算子查询耗时
└─ DBEngine.query() # 打开 sqlite3 DB → 执行查询 → 返回行
问题:16-case prefill 场景下 wall time = 32.9s。对一个纯 CPU 数据查询+计算工具而言,显然不可接受。
| 维度 | cProfile | pyinstrument |
|---|---|---|
| 采样方式 | 确定性(每次调用 hook) | 统计采样(~1ms 间隔) |
| 开销 | 5-30%(侵入式) | ≈2%(低干扰) |
| 输出 | flat 表:ncalls / tottime / cumtime | 调用树 + 按时间折叠 |
| 强项 | 精确调用次数,量化单次开销 | 快速定位热路径层级关系 |
| 弱项 | 输出扁平,大量 import 噪声 | 丢失低频调用细节 |
| 可视化 | snakeviz / speedscope | 自带 HTML / speedscope JSON |
实战中两者互补:先用 pyinstrument 看树状结构找到热路径,再用 cProfile 量化该路径上的调用次数和单次开销。
# cProfile: 输出 pstats 二进制文件
python3 -m cProfile -o /tmp/simv2.pstats -m aiak_infer_tools simulator_v2 \
--model-config ... --run-config ... --export-output /tmp/out.csv
# pyinstrument: 输出 HTML 调用树
pyinstrument -r html -o /tmp/profile.html \
-m aiak_infer_tools simulator_v2 ...
# pyinstrument: 输出 speedscope JSON
pyinstrument -r speedscope -o /tmp/profile.json \
-m aiak_infer_tools simulator_v2 ...
测量环境:macOS / Apple Silicon / Python 3.11 / sqlite3 本地文件
| 场景 | Wall Time | cProfile 下 | DB query 调用次数 |
|---|---|---|---|
| decode 1 case | — | 3.52s | 2,516 |
| prefill 16 cases | 32.9s | 35.8s | 45,424 |
<aside>
关键发现:prefill 16 cases 生成 42,960 个算子请求,但 unique DB query key 仅 556 个。
</aside>
77 倍重复查询。大部分时间花在重复打开相同的 sqlite3 文件、执行相同的 SQL、转换相同的行。这是典型的 memoization 机会。
从 pyinstrument baseline 调用树提取的热路径拆解:
| 调用路径 | 耗时 | 占 simulate 比例 |
|---|---|---|
Connection.execute(获取表名) |
9.0s | 33% |
sqlite3.connect(打开连接) |
1.7s | 6% |
Connection.close |
1.3s | 5% |
Cursor.execute(实际查询) |
2.2s | 8% |
_resolve_backend_name(listdir) |
1.7s | 6% |
_convert_row + listcomp |
2.0s | 7% |
DBEngine.query 总计 |
~22s | 81% |
dataclasses.asdict(payload 序列化) |
4.7s | 17% |
asdict on query list |
2.0s | 7% |
诊断结论: