运行¶
派发一个 CompiledProgram,并让常驻数据真的常驻。
概念¶
CompiledProgram 是一个句柄:指向编译产物,外加运行时启动它们所需的元数据。ChipWorker 持有设备连接与那些注册;派发要么是隐式的 —— 在 @pl.jit 函数上直接 kernel(*args) —— 要么是显式的,经由 worker,适用于库代码需要把 worker 传来传去、或服务化运行时想预注册许多 kernel 的场合。
早点搞清楚的一件事是:每次 launch 有什么东西跨过了 PCIe。默认每个张量实参都会 H2D 拷进去、再 D2H 拷回来。DeviceTensor 让一块 buffer 同时免掉这两次拷贝 —— 常驻权重或 KV cache 能成立,靠的就是它。
快速上手:把权重留在设备上¶
import pypto.language as pl
import torch
from pypto import ir
from pypto.runtime import ChipWorker, RunConfig
ROWS, COLS = 128, 128
PLATFORM = "__PLATFORM__"
@pl.jit
def add_kernel(a: pl.Tensor, b: pl.Tensor, out: pl.Out[pl.Tensor]):
with pl.at(level=pl.Level.CORE_GROUP):
ta = pl.load(a, [0, 0], [ROWS, COLS])
tb = pl.load(b, [0, 0], [ROWS, COLS])
pl.store(pl.add(ta, tb), [0, 0], out)
return out
torch.manual_seed(0)
A = torch.randn(ROWS, COLS, dtype=torch.float32)
B = torch.randn(ROWS, COLS, dtype=torch.float32)
# A DeviceTensor carries no shape/dtype for the @pl.jit specializer to read, so
# the resident-weight pattern below runs a *compiled* program rather than the
# jit entry directly.
compiled = ir.compile(add_kernel.lower(A, B, torch.zeros(ROWS, COLS)), platform=PLATFORM)
cfg = RunConfig(platform=PLATFORM)
with ChipWorker(config=cfg) as w:
resident = w.alloc_tensor((ROWS, COLS), torch.float32, init=B) # stays on device
for _ in range(3): # three "batches"
out = torch.zeros(ROWS, COLS, dtype=torch.float32)
w.run(compiled, A, resident, out)
torch.testing.assert_close(out, A + B, rtol=1e-4, atol=1e-4)
w.free_tensor(resident)
alloc_tensor 返回一个 DeviceTensor,编译后的程序在任何接受 torch.Tensor 的位置都接受它。运行时把这块 buffer 当作已经常驻,对该实参跳过 H2D 与 D2H。
机制¶
CompiledProgram 的契约¶
| 成员 | 给你什么 |
|---|---|
output_dir |
产物在哪 |
platform / backend_type |
它是为什么构建的;worker 会校验前者 |
param_names / output_indices / has_return |
调用形状,供自行绑定实参的 harness 使用 |
program |
交给 compile 的那份 Program —— 通常是未经 pass 的,且经 from_dir 重建后为 None |
chip_callable / runtime_name / runtime_config |
运行时侧的句柄 |
validate_ir |
逐 pass 的语义对比(精度) |
from_dir / load |
从已保存的产物目录重建句柄 |
compiled.program不是产出那些产物的那份 IR。 它是你交给compile的那个Program,原样存下;codegen 实际跑的那份变换后程序并不保留,而经from_dir重建的句柄根本没有 program。在通常的
ir.compile(MyProgram)路径上,那份输入是未经 pass 的 IR。但不必然如此 —— 上面的准备代码编译的是add_kernel.lower(...),那本身已经降级过,所以在那里compiled.program是过了 pass 的。这个属性不对此做任何承诺,它只是把收到的东西交回来。要专门拿降级后的 IR,用kernel.lower(*args)或读一份 pass dump。
显式派发¶
worker.run(compiled, *args) 是一次性的。worker.register(compiled) 返回一个跳过逐次查找的句柄,热循环要的就是它:
worker = ChipWorker(config=RunConfig(platform=PLATFORM))
try:
handle = worker.register(compiled) # eager registration
out = torch.zeros(ROWS, COLS, dtype=torch.float32)
for _ in range(3): # hot loop, no cid lookup
handle(A, B, out)
torch.testing.assert_close(out, A + B, rtol=1e-4, atol=1e-4)
finally:
worker.close() # cids + DeviceTensors released
register 触发一次装配与加载;返回的句柄才是你每次 launch 调用的东西。close() 释放这些注册,以及调用方忘记释放的 DeviceTensor。
DeviceTensor¶
| 规则 | 细节 |
|---|---|
| 由 worker 分配 | w.alloc_tensor(shape, dtype, init=...) |
| 不会自动拷回 | 用 w.copy_from(host_ptr, t.data_ptr, t.nbytes) 读回来 |
| 显式释放 | w.free_tensor(t);close() 是兜底,不是方案 |
| 绑定在它的 worker 上 | 不能移交给另一个 ChipWorker |
影响派发的 RunConfig 字段¶
RunConfig 同时携带编译侧与运行时侧设置;编译侧那些见上一页。派发时要紧的是:
| 字段 | 效果 |
|---|---|
platform / device_id |
用哪块设备,以及 worker 会接受哪种产物 |
enable_chip_swimlane / enable_dep_gen / enable_pmu / enable_dump_args / enable_scope_stats |
DFX 采集(性能) |
ring_task_window / ring_heap / ring_dep_pool |
运行时环的尺寸(内存) |
aicpu_thread_num |
AICPU 线程数覆盖 |
RunConfig 里有些字段属于 harness,不属于派发。 rtol / atol、golden_data_dir、save_kernels 与 codegen_only 是由系统测试 harness 读取的 —— 那条路径会编译、生成 golden 并比对。走 compiled(...)、worker.run(...) 或注册句柄时,它们不起作用;尤其是 codegen_only=True 在这条路径上并不会阻止派发,别指望用它来避免一次 launch。(save_kernels_dir 是例外:RunConfig.compile_kwargs() 会把它作为 ir.compile 的 output_dir 转发。)
边界情况¶
| 现象 | 原因 | 修法 |
|---|---|---|
| worker 在首次派发前就拒绝该程序 | 产物的 platform 与 worker 不一致 |
用你要派发的平台去编译 |
missing inferred tensor metadata for parameter |
把 DeviceTensor 传给了 @pl.jit 入口 |
派发已编译的程序;特化器读不到它的 shape/dtype |
| 设备内存随 launch 增长 | DeviceTensor 没释放 |
free_tensor,或用 with 圈住 worker |
| 派发不给 host/device 拆分 | execution_time 是整段墙上时间 |
用 pypto.runtime.benchmark 取 device_wall_us / host_wall_us |