调试与陷阱¶
分布式 bug 很少留下本地堆栈——症状出现在某个 rank 上,而原因却在另一个 rank 上。
常见故障模式¶
| 症状 | 可能原因 | 修复 |
|---|---|---|
| 所有 rank 挂起 | notify/wait 顺序错误 | 确保 notify 循环在 wait 循环之前。若某 rank 的 wait 与它自己的 send 分属不同派发,也可用 pl.submit(..., deps=[send_task]) 显式钉住顺序——见声明一条边。 |
| 静默数据损坏 | remote_load offsets 或 shape 不匹配 |
验证 offsets 与对端的 store offsets 对齐。 |
| Signal cell 永不达到期望值 | 错误 NotifyOp |
多参与者屏障用 AtomicAdd;1:1 交换用 Set。 |
| 编译时形状不匹配 | NR 未使用 pl.dynamic |
将运行时维度包裹在 pl.dynamic("NR") 中。 |
派发时抛出 TypeError |
IO buffer 在 prepare() 前未调用 .share_memory_()——fork 出的子进程看不到 fork 之后分配的 buffer |
在 prepare() 之前对每个传给 worker 的 host tensor 调用 .share_memory_()。 |
致命陷阱¶
缺少
.share_memory_(): 传入DistributedWorker的 IO buffer 在prepare()前必须调用.share_memory_()。若忘记调用,运行时会在派发时 抛出TypeError——fork 出的子进程无法访问父进程私有的内存。
alloc_window_buffer传入 rank 数量而非字节数:size参数以字节 为单位。使用 shape+dtype 重载。分发循环的执行次数与
device_ids不匹配:device_ids是物理卡 ID (例如来自--device 4,5),不要求从 0 开始或连续。device=r则是一个 逻辑 rank 索引,始终按[0, world)校验,其中world = len(device_ids);运行时按rank r -> device_ids[r]映射。真正 重要的不变量是分发循环的执行次数等于len(device_ids)——写成for r in pl.range(pld.world_size())时会自动满足。不匹配的例子:device_ids=[0, 1, 2, 3](4 张卡)但分发循环只覆盖range(2),会让 2 张卡未被派发,导致未定义行为(MaterializeCommDomainScopes要求device=r循环的范围必须是[0, N))。
诊断标志¶
SIMPLER_HOST_STRACE 和 SIMPLER_DFX 是编译时 C 预处理器宏,设置为 shell
环境变量无效——它们在编译期就已固定。默认开启。切换它们属于 simpler
运行时的构建配置变更,而非一个简单的 cmake -D... 缓存变量——具体机制见
simpler 运行时自己的构建文档。
运行时环境变量:
分布式 DFX 入口点¶
- L2 swimlane:
RunConfig(enable_chip_swimlane=True)——在 worker 内部启用 逐任务计时,并透传到 L3 编排。写入dfx_outputs/chip_swimlane_records.json(onboard 场景会与下面的依赖图合并为merged_swimlane_*.json)。 - Scope 统计:
RunConfig(enable_scope_stats=True)——写入dfx_outputs/scope_stats/scope_stats.jsonl,包含 task_window、heap 和 tensormap 水位。 - 依赖图:
RunConfig(enable_dep_gen=True)——写入dfx_outputs/deps.json, 供调度器分析的任务依赖图。
相关链接¶
- 00-model — 快速开始和模型词汇
- 02-primitives — 集合通信的底层基础