运行期 DFX¶
旧 Python 接口示例: 本页为兼容现有用法保留
platform=调用, 这些调用会发出DeprecationWarning;新代码应使用backend_type和execution_mode。参见目标映射。
五个互相独立的采集开关。每个记录什么、落在哪、用什么打开。
概念¶
本页所有东西都要花掉一次运行。编译器已经告诉过你它决定了什么(见先看便宜的那两样);这些开关记录的是硬件做了什么。
它们是 RunConfig 上五个完全独立的开关,可以任意组合。开启其中任意一个都会强制 save_kernels=True,这样输出目录在运行结束后仍然存在,产物还在。
所有产物落在同一个目录下:
<work_dir>/dfx_outputs/
├── chip_swimlane_records.json enable_chip_swimlane
├── merged_swimlane_*.json (onboard only — the joined trace)
├── deps.json enable_dep_gen
├── args_dump/ enable_dump_args
├── pmu.csv enable_pmu
└── scope_stats/scope_stats.jsonl enable_scope_stats
快速上手¶
最先要的是两个开关,而且要一起开 —— 时序,以及时序所属的那张任务图:
from pypto.runtime import RunConfig
cfg = RunConfig(
platform="a2a3",
enable_chip_swimlane=4, # per-task timing, full collection
enable_dep_gen=True, # the task DAG the timing is joined against
save_kernels=True, # forced anyway; be explicit about wanting the directory
)
out = kernel(a, b, torch.zeros(...), config=cfg)
从 pytest 用同样两个:
机制¶
开关矩阵¶
RunConfig 字段 |
pytest flag | dfx_outputs/ 下的产物 |
用什么打开 |
|---|---|---|---|
enable_chip_swimlane: int |
--enable-chip-swimlane(= 4)/ --chip-swimlane-level N |
chip_swimlane_records.json |
PyPTO Toolkit;板上会自动产出 merged_swimlane_*.json |
enable_dep_gen: bool |
--enable-dep-gen |
deps.json |
python -m simpler_setup.tools.deps_viewer |
enable_dump_args: int |
--dump-args [LEVEL](裸写 = 1) |
args_dump/ |
python -m simpler_setup.tools.dump_viewer |
enable_pmu: int |
--enable-pmu [N](裸写 = 2) |
pmu.csv |
任意 CSV 阅读器 |
enable_scope_stats: bool |
--enable-scope-stats |
scope_stats/scope_stats.jsonl |
python runtime/simpler_setup/tools/scope_stats_plot.py |
没有一个渲染器是自动跑的。 Graphviz 对上千节点的图做布局可能要几分钟,而把它放在派发热路径上跑,曾导致外层调度器把整个作业树 SIGKILL 掉。运行器只会把渲染命令打出来,你想看图时自己跑。
泳道图:采集是分级的¶
enable_chip_swimlane 是等级,不是开关:
| 等级 | 增加 | 解锁 |
|---|---|---|
0 / False |
— | 关 |
1 |
AICore 每任务 start / end,以及任务记录缓冲 | 每任务泳道 |
2 |
+ AICPU 打点的 dispatch / finish | [dispatch, start] 取件间隙 |
3 |
+ 调度器主循环阶段记录 | sched_overhead_analysis、Toolkit 的 Scheduler View |
4 / True |
+ 编排器阶段记录 | Toolkit 的 AICPU Orchestrator 视图 |
每一级都是采集器里的真实 guard,不是冗余度设置。 等级 1 根本不打 dispatch / finish 时间戳,任何后处理都恢复不出来 —— 「先便宜地采,回头再深入分析」是行不通的。反过来,等级越高对时序的扰动越大。取能回答你问题的最低等级。
True 表示等级 4,与裸写的 --enable-chip-swimlane 一致。超范围的等级会让 RunConfig 抛 ValueError。
打开之后该看什么,见读泳道图。
泳道图:板上会把你的 workload 跑两遍¶
converter 需要把每任务时序与一张只有 deps.json 才带的任务图做 join —— 设备热路径已不再记录每任务的 fanout。但 dep_gen 采集会扰动泳道所要测量的那个时序。于是两次采集来自两次独立运行,而 PyPTO 替你做了这个拆分:
- 抓图那遍 —— 只开 dep_gen,产出
deps.json。L2 上这一遍在独立子进程里跑,因为运行时不能可靠回收采集器申请的 SVM host-register 映射,同进程内第二次 DFX 运行会撞上注册上限。这一遍是 best-effort:失败则记一条警告,计时那遍照常跑,泳道退化为匿名的task(rXtY)。 - 计时那遍 —— 泳道(以及其他对时序敏感的 DFX),dep_gen 强制关闭,产出被报告的那份
chip_swimlane_records.json。
值得记住的后果:
- 绝不要从开了泳道的板上运行里读墙钟时间。 单独跑一次干净的,或用
benchmark。 - 两遍都会真的执行程序,且两遍之间不会恢复可变参数。
- 额外加
--enable-dep-gen不会改变这两遍的行为 —— 抓图那遍已经产出了deps.json。它只是让运行额外打印deps_viewer的提示。 - 仿真器平台(
*sim)保持单遍,只产出chip_swimlane_records.json;仿真器还没有 converter 需要的任务元数据。用仿真器看调度的形状,时序本身的问题上板看。
参数 dump:标记要看的张量,不要全量拿¶
enable_dump_args 是等级:0 关,1 部分,2 全量。
优先用部分。 等级 2 会写下每个任务的每一个 binding;在大 workload 上这会打满 host 侧的 dump 收集器(约 42 MB/s 排空速率),AICPU 随后被 STARS op-execute 超时杀掉 —— 一个 1 GB 的 KV-cache binding 填队列的速度远快于排空。
部分 dump 只采集你标记的东西,通过两个与 deps= 完全对称的面:
# Declarative marker — every SUBSEQUENT dispatch consuming that exact value dumps it.
pl.dump_tag(q)
pl.dump_tag(out)
out = self.qk_pv(q, k_cache, out) # q and out dumped; k_cache filtered out
# Explicit kwarg — this one launch only.
with pl.manual_scope():
out, tid = pl.submit(self.qk_pv, q, k_cache, out, deps=[prev], dumps=[q, out])
同一个 dumps=[...] kwarg 也可以写在派发 scope 上 —— pl.at(...)、pl.spmd(...)、
pl.cluster(...) 或 pl.graph(...) —— 标记该 scope 启动的 task。
标记按 Var 身份追踪,绝不按名字,所以它能穿过 SSA、内联和 codegen —— 也因此,被重新绑定或变换过的值不被覆盖:
| 你写的 | 覆盖吗? |
|---|---|
pl.dump_tag(q) 然后 self.k(q, ...) |
是 |
pl.dump_tag(q) 然后 q = self.foo(q) 然后 self.k(q, ...) |
否 —— 重新绑定的结果是新值,要重新打标 |
pl.dump_tag(q) 然后 q2 = pl.reshape(q) 然后 self.k(q2, ...) |
否 —— 给 kernel 实际收到的那个值打标 |
在普通的 self.kernel(...) 调用上写 dumps= |
否 —— 抛 ParserTypeError;改用 pl.dump_tag 或 pl.submit(..., dumps=[...]) |
在 InCore / AIC / AIV 函数体里写 pl.dump_tag |
否 —— 解析期抛 ParserSyntaxError;放到外层的 Orchestration 或 Inline 函数里 |
dump 关闭时标记是惰性的,全量 dump 下则无关紧要。完整的限制表见 Runtime DFX Flags。
PMU¶
一次运行一个事件类型,写入 pmu.csv。以比 in-core trace 更粗的粒度回答「各 pipe 在干什么」,而且不需要仿真器。
Scope 统计¶
per-scope 的 heap / task_window / tensormap 环形缓冲填充峰值,每个 scope 一条 JSONL 记录:
python runtime/simpler_setup/tools/scope_stats_plot.py \
<work_dir>/dfx_outputs/scope_stats/scope_stats.jsonl
这是 ring 定容背后的测量 —— 在动 ring_task_window、ring_heap、ring_dep_pool 之前先读它。见内存。
查看器里的 kernel 名字¶
默认情况下泳道和依赖查看器用数字 id 标注任务。只要开了泳道或 dep_gen,PyPTO 就会从 kernel_config.py 合成 dfx_outputs/name_map_<case>.json,两个工具都会自动拾取 —— 所以任务直接显示真实 kernel 名,不需要手工步骤。
分布式运行¶
L3 按每次派发给前缀加命名空间,因为一块芯片在一次 host 编排里可能收到多次派发,否则它们会互相覆盖:
<work_dir>/dfx_outputs/rank0/d0/ # rank 0, its 0th dispatch
<work_dir>/dfx_outputs/rank0/d1/
<work_dir>/dfx_outputs/rank1/d0/
每个叶子目录里放着上面那张表里的扁平产物,所以本页内容在单次派发目录内原样适用。
边界情况¶
| 现象 | 原因 | 处理 |
|---|---|---|
根本没有 dfx_outputs/ |
没开任何开关,或运行在派发前就失败了 | 开任一开关都会强制 save_kernels;先确认运行真的到了设备 |
| 墙钟时间比预期差很多 | 板上泳道把 workload 跑了两遍 | 用单独一次干净运行来测 |
没有 merged_swimlane_*.json |
仿真器平台 | 符合预期 —— *sim 只产出 records |
泳道全是匿名的 task(rXtY) |
抓图那遍失败了,没有 deps.json |
从日志里那条警告去定位 —— 加 --enable-dep-gen 不会重跑任何东西,那一遍本来就会自己跑 |
| AICPU 中途被杀,STARS 超时 | 大 workload 上开了 enable_dump_args=2 |
降到等级 1,并用 pl.dump_tag 标记需要的张量 |
| 打了标的张量没出现在 dump 里 | 标记按 Var 身份追踪,而该值被重新绑定或变换过 | 给 kernel 实际收到的那个值打标 |
deps_viewer --format html 卡住 |
超大图上跑 Graphviz dot |
换 --engine sfdp(O(N log N),可扩展到 1 万+ 节点) |
enable_l2_swimlane 的 DeprecationWarning |
旧拼写 | 改名为 enable_chip_swimlane;取值与语义不变 |
参见¶
- 读泳道图 —— 打开之后该看什么。
- 回放一次构建 —— 本页每个开关在回放路径上原样适用。
- In-core trace —— 再往下一层,深入单个 kernel 内部。
- 内存 ——
scope_stats测量的那些 ring。 - 精度 ——
enable_dump_args在收敛流程里的位置。 - Runtime DFX Flags ——
CallConfig契约、完整的 dump-tag 限制表,以及废弃别名。