调度调优¶
搞清运行时拿你的图做了什么,然后凭证据去改。
前置:塑形任务图。
你要做的东西¶
一套能套用到自己 kernel 上的循环:看图 → 看并发 → 看资源水位 → 改一处 → 再测一次。
第 1–4 步都是各写出一个文件的 RunConfig 开关,其中没有任何一步需要你去改造 kernel。第 5、6 步则是改变工作本身的组织方式,不是观测。
四个观测点¶
| 问题 | 开关 | 产物 |
|---|---|---|
| 图长成什么样? | enable_dep_gen=True |
<work_dir>/dfx_outputs/deps.json |
| 任务真的重叠了吗? | enable_chip_swimlane=True |
<work_dir>/dfx_outputs/chip_swimlane_records.json |
| 运行时的环是否接近满? | enable_scope_stats=True |
<work_dir>/dfx_outputs/scope_stats/scope_stats.jsonl |
| 哪条 pipe 是瓶颈? | enable_pmu=2 |
<work_dir>/dfx_outputs/pmu.csv |
它们可以组合 —— 一次运行可以同时采集多项。按顺序来;每一项回答的问题都是下一项的前提。
第 1 步:图是你想要的那张吗¶
from pypto.runtime import RunConfig
kernel(a, b, out, config=RunConfig(platform="a2a3sim", enable_dep_gen=True))
然后渲染它:
viewer 默认输出文本,要图视图得传 --format html。
这是第一件要查的事,因为后面每一项度量都在它的下游。两种读法值得掌握:
- 该扇出的地方是一条链 —— 一条并非真实依赖的推导边正在把你串行化。回到 上一页第 3 步。
- 该是链的地方是扇出 —— 没有任何东西在给必须有序的任务定序。这是潜伏的竞态,而且它不会每次都表现为错误答案。
第 2 步:它们真的重叠了吗¶
一张并行的图并不保证并行的执行。swimlane 给出逐任务时序:
模拟器注意事项: 在
*sim平台上这是单趟的,只产出chip_swimlane_records.json。合并后的merged_swimlane_*.json视图会被有意跳过,因为模拟器尚未提供转换器所需的任务元数据。在真机平台上同一个开关会把负载跑两遍 —— 先一趟 dep_gen 采集图,再一趟干净的计时 —— 因为采集会扰动时序。
第二点对基准测试很重要:不要从开了 swimlane 的真机运行里读挂钟时间。
第 3 步:环是不是瓶颈¶
运行时用环来存放在飞的任务。如果某个环饱和了,往图里加并行度不会有任何收益:
它按作用域报告三个环的峰值 —— task_window(在飞任务槽)、heap(输出存储)、tensormap。峰值顶到容量就是该调大那个环的信号;峰值远低于容量则说明环不是你的问题。
第 4 步:改一处¶
每个环都有对应的覆盖项。它们是逐次调用生效的,所以你可以不重新编译就扫参:
| 旋钮 | 单位 | 约束 |
|---|---|---|
ring_task_window |
在飞任务槽数 | 2 的幂,>= 4 |
ring_heap |
字节 | 2 的幂,>= 1024 |
ring_dep_pool |
依赖边容量 | [4, INT32_MAX] |
aicpu_thread_num |
AICPU 线程数 | 默认沿用编译期的 RUNTIME_CONFIG |
每一项都接受一个标量(广播到全部四个 scope-depth 环)或恰好 4 个 int 的列表,分别设定环 0..3,其中 0 表示该环保持默认。默认值 None 表示不设该字段,运行时回落到它的编译期默认值;旧的进程级 PTO2_RING_* 环境变量已经退役,不再被读取。
ring_heap 以字节计而 ring_task_window 以槽计,是最容易犯的错:ring_heap=64 不是 64 个缓冲区,而是 64 字节,会因为不足 1024 而被拒。
第 5 步:任务粒度¶
上面那些旋钮调的是机器本身的尺寸。更大的杠杆通常是一个任务承载多少工作:
- 太细 —— 逐任务的调度开销占主导,task_window 饱和在记账而非工作上。
- 太粗 —— 任务数少于核数,无论环怎么调,图都填不满机器。
SPMD 派发上的 sync_start=True 要求所有 block 原子启动。它换来跨 block 明确定义的起点,代价是失去让任何 block 提前开始的能力 —— 所以 sync_start 的任务自身不能被逐 block 预置,不过给它标上 allow_early_resolve=True 仍能让它的消费者预置。
第 6 步:别把 setup 付两遍¶
worker 的 setup 是按 worker 而非按 program 计的。若干 program 注册到同一个 worker 上就能共享它,这样第一次之后的每次运行都省掉一整次 setup —— 形态见 examples/runtime/multi_program_kv_cache.py(一个 prefill 与一个 decode program 共享一份 KV cache 和一个 worker)。
调错误答案,而不是调慢¶
同一族里还有两个开关,面向正确性而非速度:
enable_dump_args=1只 dump 你用pl.dump_tag(t)(或pl.submit(..., dumps=[...]))标记的张量,落到<work_dir>/dfx_outputs/args_dump/。用python -m simpler_setup.tools.dump_viewer查看。enable_dump_args=2dump 每个任务的全部输入输出。
致命陷阱: 在大负载上做全量 dump 会把主机侧收集器打满(约 42 MB/s 的排空速率),并让 AICPU 被 STARS op-execute 超时杀掉。优先用等级 1 加上对具体张量的
pl.dump_tag(t)。
这个循环¶
deps.json → 图对不对? → 改边 (04-task-graph)
swimlane → 重叠了吗? → 改粒度
scope_stats → 某个环饱和了吗? → 调大那个环
pmu.csv → 哪条 pipe 是上限?→ 改 kernel(02、03)
每改一处就重测一次,且一次只改一处 —— 这四项观测彼此不独立,同时改两处通常会让你无法归因。