Rust in Everything
基于 tenferro-runtime 0.8.0

cannot map label occurrences [105, 105] into [105]

主线位置:einsum 线的第 3 步(执行)的最后一小步。收缩做完以后,结果的轴要按输出下标重排;输出里重复的标签(ii)表示「放到对角线上」,要多一步嵌入。执行器的各条路见 einsum。

flowchart TB
  sub["输出下标里 i 出现两次"] --> n{"几个输入"}
  n -->|一个| one["单输入路径"]
  one --> emb["embed_repeated"]
  emb --> ok1["i->ii 能算"]
  n -->|两个| two["binary_contract"]
  two --> tr["只做转置"]
  tr --> e2["cannot map label"]
  n -->|三个以上| ce["cpueinsum"]
  ce --> e3["output label repeated"]

实验 crate exp-tenferro8/pit1-pit,bin p14_repeated_out,release;CpuEngine::serial();M2 Max、macOS 27、rustc 1.99.0。

现象

tenferro-einsum 的 crate 文档列出它支持的重复标签写法:「"ii->i" extracts diagonals, "ii->" traces, and "i->ii" embeds a vector on a diagonal」。单输入确实能算;想把两个向量的逐元素积放到对角线上,写 "i,i->ii",就不行了(x = [1, 2],y = [3, 4],z = [5, 6]):

let mut engine = CpuEngine::serial();
[&x].einsum("i->ii", &mut engine)
[&x, &y].einsum("i,i->ii", &mut engine)
concrete i->ii               Ok([2, 2] [1.0, 0.0, 0.0, 2.0])
concrete i,i->ii             Err(eager_einsum: invalid argument configuration: cannot map label occurrences [105, 105] into [105])
  debug: Tensor(Validation { op: "eager_einsum", source: InvalidArgument { argument: "configuration", message: "cannot map label occurrences [105, 105] into [105]" } })
concrete i,i,i->ii           Err(CPU einsum: backend failure: output label 105 is repeated)
concrete ij,j->iij           Err(eager_einsum: invalid argument configuration: cannot map label occurrences [105, 105, 106] into [105, 106])

(105 是 i 的码值,106 是 j。)三个输入报的是另一条错误。eager 和 traced 入口也一样失败,eager 的错误来自另一个模块:

eager i,i->ii                Err(einsum planning failed: invalid einsum planning configuration: einsum builder: cannot map label occurrences [105, 105] into [105])
traced i,i->ii               Err(eager_einsum: invalid argument configuration: cannot map label occurrences [105, 105] into [105])

出处

单输入的路径在规约之后调 embed_repeated:输出里某个标签出现的次数比当前张量多,就 embed_diagonal 一次,插入一个同名的轴,然后才按输出下标转置。

两个输入走 binary_contract。它收缩完得到标签 [i] 的结果,最后一步直接 transpose_to_labels(exec, result, &target_labels),中间没有嵌入。转置要给目标的每一次出现找一个不同的源轴,map_label_occurrences 的注释写着「Map each source label occurrence to a distinct matching target axis」;[i, i] 有两次出现,[i] 只有一个轴,映射不出来,map_label_axes 就报了上面的错。eager 入口先试 try_expand_eager_einsum 把收缩展开成核心算子图,builder.rs 里同名的 map_label_axes 报的是同一句话,只是包在 planning 错误里。

三个输入整条交给 cpueinsum(条件见 label 106 has extents 1 and 3),它不接受重复的输出标签,是 cpueinsum 0.9.0 的 SpecError::OutputRepeated。

上游的设计文档 contraction-pipeline.md 写的一般路径是六步,第 5 步是「Embed repeated output labels with embed_diagonal」;代码里只有单输入的路径做了这一步。文档和代码不一致,可以算上游 bug,0.8.0 上复现。用户指南 docs/guides/einsum.md 说 "i->ii" 是「a tenferro extension to the common NumPy/PyTorch einsum surface」(NumPy 不接受输出里重复的标签),从 NumPy 搬来的代码不会写出这种下标,自己写的才会。

修法

先按不重复的输出收缩,再单独嵌入对角线:

let xy = [&x, &y].einsum("i,i->i", &mut engine)?;
let d1 = xy.embed_diag(0, 1, &mut engine)?;      // TensorSessionOpsExt
let d2 = [&xy].einsum("i->ii", &mut engine)?;   // 单输入的 einsum 也行
fix i,i->i then embed_diag   Ok([2, 2] [3.0, 0.0, 0.0, 8.0])
fix i,i->i then i->ii        Ok([2, 2] [3.0, 0.0, 0.0, 8.0])

embed_diag(axis_a, axis_b, engine) 把第 axis_a 轴放到 axis_a、axis_b 两个轴的对角线上,结果是列主序的 diag(3, 8)。"ij,j->iij" 这类更一般的写法同理:先 "ij,j->ij",再在第 0 轴上嵌入。

怎么确认没中招

  • 错误里出现 cannot map label occurrences [a, a, ..] into [a, ..],或者 output label .. is repeated:检查输出下标里有没有重复的字母。
  • 输出里需要重复标签时,只在单输入的 einsum 上写,或者直接调 embed_diag。

小结

  • 单输入的 "i->ii" 能算,两个以上输入的 "i,i->ii" 不能:两输入路径收缩后只做转置,不嵌入对角;三输入以上交给不接受重复输出标签的 cpueinsum。
  • 设计文档说一般路径会嵌入重复的输出标签,代码只在单输入路径做了;上游 bug,0.8.0 复现。
  • 先收缩成不重复的输出,再 embed_diag。

💬 关联讨论

发起讨论
加载中...