no engine "tenferro-cpu.default.v1" is registered
主线位置:traced 主线的执行一步,
Runtime::builder()→register_engine→prepare→run(&mut [&mut engine], ...)。Runtime里有什么、为什么引擎由调用方持有,见 执行与缓存;CpuEngine本身见 engine 与 threadteam 池;运算族的执行模块见 扩展算子。
flowchart TB
prog["CompiledGraph"] --> prep["prepare"]
prep --> reg{"登记了引擎描述"}
reg -->|没有| e1["没登记引擎"]
reg -->|有| fam{"有运算族算子"}
fam -->|没有| run["run"]
fam -->|有| mod{"装了执行模块"}
mod -->|没有| e2["缺执行模块"]
mod -->|装了| run
run --> cnt{"engine 个数对"}
cnt -->|不对| e3["engine 个数不符"]
实验 crate exp-tenferro8/pit2-exp,bin p19_engine,CPU(CpuEngine::serial()),debug,M2 Max、macOS 27、rustc 1.99.0。
现象
两个程序,y = a·a(核心算子)和 z = inv(a)(linalg 的扩展算子),在三种 Runtime 上准备、执行。engine 是调用方自己的 CpuEngine:
let mut engine = CpuEngine::serial();
let (py, pz) = (c.compile(&a.matmul(&a)?)?, c.compile(&a.inv()?)?);
let empty = Runtime::builder().build()?; // 1. 什么都没登记
let mut b = Runtime::builder();
b.register_engine(tenferro_cpu::runtime_engine_registration(&engine)?)?;
let cpu_only = b.build()?; // 2. 只登记了 CPU 引擎
// 每个都 rt.prepare(&p, &[], &[engine.target()]) 再 rt.run(&mut [&mut engine], &prepared, &[])
no engine, matmul: prepare Err: Runtime::prepare (Execution): runtime state failure: no engine "tenferro-cpu.default.v1" is registered with this runtime
Debug: RuntimeState { op: "Runtime::prepare", phase: Execution, message: "no engine \"tenferro-cpu.default.v1\" is registered with this runtime" }
cpu engine, matmul: Ok([17.0, 7.0, 7.0, 10.0])
cpu engine, inv: prepare Err: Runtime::run (Execution): runtime state failure: missing extension family "tenferro-linalg.linalg.v1"
Runtime::builder().build()成功,建图、编译也成功;错误推迟到第一次prepare。- 引擎明明传给了
prepare(engine.target()),run时也会传;报错说的是另一件事:Runtime自己没有这个引擎 id 的描述。 - 只登记了 CPU 引擎时,矩阵乘能跑,
inv报缺 family。报错只给 family id,不说是哪个 crate 的哪个函数能补上。 - 第二条错是
prepare返回的,消息却标着Runtime::run。
准备好之后,run 还会核对传进来的 engine 个数:
run with 0 engines: Err: Runtime::run (Execution): invalid argument engines: the program was prepared for 1 engines, but 0 were passed
run with 2 engines: Err: Runtime::run (Execution): invalid argument engines: the program was prepared for 1 engines, but 2 were passed
出处
0.8.0 的 Runtime 不持有引擎(执行与缓存 的「Runtime:快照加缓存,没有引擎」)。register_engine 只登记一份描述:引擎 id、它能执行什么、上下文类型。prepare 对每个目标先到快照里查这份描述:
let view = snapshot.engine(target.engine_id()).ok_or_else(|| {
Error::runtime_state(
CALLER,
ErrorPhase::Execution,
format!(
"no engine {:?} is registered with this runtime",
target.engine_id().as_str()
),
)
})?;
查到之后才进入真正的准备:逐个运算找能执行它的提供者,扩展算子要求同一引擎 id 上装了同一 family 的执行模块,找不到就是 PrepareError::MissingExtension。准备阶段的错误统一经过 prepare_error 包装,那里把调用方写死成了 "Runtime::run",所以 prepare 的失败读起来像是 run 的。这是一处措辞上的上游问题,不影响错误类型。
eager 一侧没有这一步:EagerRuntime 第一次见到一种 engine 时自己登记它的描述,linalg、einsum、FFT 的模块也在第一次用到时自动安装(扩展算子 的「接进 traced 和 eager」)。
修法
登记引擎描述,再给每个用到的运算族装执行模块,引擎 id 用 CPU 的:
let mut b = Runtime::builder();
b.register_engine(tenferro_cpu::runtime_engine_registration(&engine)?)?;
b.install_extension_module(tenferro_linalg::extension_module::<CpuEngine>(tenferro_cpu::runtime_engine_id()?)?)?;
let full = b.build()?;
cpu engine + linalg module, inv: Ok([0.2727272727272727, -0.09090909090909091, -0.09090909090909091, 0.36363636363636365])
- 描述只按类型和 id 登记:同一个
Runtime可以拿单线程和多线程的CpuEngine去跑,两者的target()相等(执行与缓存)。run时传的 engine 个数要和prepare时的目标个数一样。 - 执行模块管「能算」,
AdContext里的规则管「能求导」,两件事分开装;只装了模块、求导时还会碰到 unsupported vjp AD rule。 - 自定义扩展算子在 eager 下要调
EagerRuntime::install_extension_module(扩展算子)。
怎么确认没中招
- 建
Runtime的代码集中到一个函数里,列出所有用到的运算族(grep -E 'TracedTensor(Linalg|Fft)Ext|einsum'),每个都对应一行install_extension_module。 - 启动时对一个最小程序(每个运算族一个运算)跑一次
prepare,在真正的计算之前发现缺什么。 - 看到
Runtime::run字样的错误先别找run的调用:missing extension family和其他准备阶段的错误也这样标。
小结
Runtime::builder().build()不带任何引擎描述;没有register_engine时prepare报no engine "tenferro-cpu.default.v1" is registered with this runtime。- 运算族的算子还要装执行模块,否则报
missing extension family "<id>";这条来自prepare,消息却标着Runtime::run。 run传的 engine 个数必须等于prepare的目标个数。eager 自动登记,不会遇到前两条。