跳转至

L3 持久执行

通常情况下,准备好的分布式程序会复用同一个 Simpler worker,但每次 dispatch 仍会重新进入一次 Worker.run()。因此,包含通信窗口的程序会在每次调用时申请并 释放 CommDomain。

使用 persistent=True 可以在 prepared worker 的整个生命周期内保留 generated CommDomain:

with decode.prepare(persistent=True) as worker:
    for _ in range(100):
        worker(x, weights, output)

持久模式需要显式开启,默认的 prepare() 行为保持不变。

生命周期

每次 PyPTO 提交都会在调用线程中同步构建持久 orchestration,并直接调用 Simpler Worker.submit()。返回的 DistributedRunHandle 会持有该请求,直到 native completion fence 和清理全部结束。首次使用某个 generated CommDomain 时会申请物理 window,后续调用则获得同一个 handle 的 retained lease。关闭 prepared worker 时 会先停止接收新请求、排空所有已发布 handle,再释放全部 retained domain。请求和 domain 释放错误都会抛给调用方。

生成的 HOST orchestration entry 接受内部参数 _domain_provider。普通 dispatch 不传该参数,仍然调用 orch.allocate_domain;持久 dispatch 则传入一个按 compiled program 和 generated domain name 隔离的 provider。已有 generated artifact 必须 重新生成后才能使用持久模式。

Window 内容

持久执行默认会在复用前将 retained CommDomain window 恢复为全零,使每次重复 dispatch 都获得 fresh-window 语义,包括通信 buffer 中保存同步状态的程序。 第一次 dispatch 使用 runtime 新申请并初始化的 window,不执行额外 reset。

只有程序能够自行管理复用的通信 buffer 内容时,才应关闭 reset:

with decode.prepare(
    persistent=True,
    reset_persistent_windows=False,
) as worker:
    worker(x, weights, output)

关闭 reset 后,后续 dispatch 会原样看到上一次留下的内容。调用方必须在复用前 手动清零相关通信 buffer,或者使用 epoch 等协议安全管理所有 retained signal 和 data 状态;如果两者都没有,复用陈旧状态可能导致错误结果或死锁。

默认开启 reset 时,PyPTO 会在复用前同步清零所有参与 worker 上的具名本地 buffer。 每次 reset 请求会按不同的具名 buffer 大小各创建一个全零的 POSIX 共享内存 host Buffer,并在所有相同大小的 domain 和 worker 之间复用。staging buffer 会保留到 Worker.run() fence 返回后再释放,因此 staging 内存峰值是这些不同大小之和。 reset copy 会计入每次重复请求的 host 开销。

PyPTO 使用 copy_to(dst, src) 重置每个具名 buffer,默认拷贝长度为源 Buffer 的大小。生成的 domain 由具名 buffer 完整覆盖请求的 window;如果请求范围内含有 未命名的空隙,reset 会直接拒绝,因为该区域没有可用于恢复内容的具名 buffer。 运行时报告的物理映射(physical mapping)可能因分配对齐而大于请求大小,A5 就属于 这种情况。reset 接受这部分额外容量,只拷贝具名 buffer,不暴露或重置物理映射尾部 的填充区。如果物理映射小于请求的 window,则会在重置该 domain 的 buffer 之前报错。

多 compiled program

持久模式支持现有的 multi-program prepared worker:

with prefill.prepare(extra_compiled=[decode], persistent=True) as worker:
    worker.run(prefill, prefill_x, weights, kv_cache)
    worker.run(decode, decode_x, weights, kv_cache)
    worker.run(decode, decode_x, weights, kv_cache)

Domain 按 (compiled program, generated domain name) 隔离。因此,即使 prefill 和 decode 都生成了 comm_d0,它们仍然使用不同的物理 domain。所有 prepared program 仍须满足原有的 platform、runtime 和 device ID 兼容性检查。

DistributedWorker.submit() 和 Simpler 仍会串行构建 graph。请求被接受后,持久 模式与普通模式遵循相同的有界异步分发约定:PyPTO 最多发布两个 metadata frame, 实际可重叠的 device run 数量由 backend 协商出的 depth 决定。

Runtime 依赖

该实现不修改 Simpler。每个请求都使用公开的 Worker.submit() boundary,其 native handle 会一直挂在 PyPTO 的有界 dispatch handle 上直到完成。每个请求通过 Orchestrator.get_or_allocate_domain(..., retain_after_run=True) 获取通信域 (CommDomain)。Simpler 在复用时验证配置,将通信域登记到当前运行的完成栅栏 (completion fence),并在成功运行之间保留其分配。prepared worker 关闭时, PyPTO 等待所有 dispatch handle 完成后释放通信域;失败时由 Simpler 负责清理。 PyPTO 不再修改私有的通信域 registry 或 run-resource journal。