Rust in Everything
基于 tenferro-linalg 0.8.0

线性代数

这一部分落在 TRG 主线的「一步 SVD 截断加收缩」和「分段求导得内能」上。TRG 每一步做两次 SVD,留前 k 个奇异值和奇异向量,再收缩成新的局部张量;求内能时,反向要穿过每一次 SVD。JAX 的 jnp.linalg 和 tenferro 的 TracedTensorLinalgExt 在普通矩阵上给出同样的梯度,差别出在三处:奇异向量的符号,奇异值重复或为 0 时的求导规则,以及截断 SVD 输出时的形状元数据。每页都是同一个任务的两份程序(有的地方加一份 PyTorch),数字从实测输出里贴。分解怎么落到 faer、反向规则怎么写,见 tenferro 卷的线性代数。

  1. 分解的梯度:svd、eigh、qr、solve 四个损失,两边对中心差分都在 1e-9 量级,彼此差 1e-15 量级;奇异向量、特征向量的符号不同,所以损失要和符号无关,SvdGauge::CanonicalPivot 能定一个符号;秩亏的测试矩阵两边都对不上差分,恰好为 0 的奇异值两边都给 NaN;solve 的右端项必须是二维的。
  2. 简并奇异值:TRG 从第 5 步起,JAX 内置 jnp.linalg.svd 和 PyTorch 的梯度是 NaN,因为第 4 步的矩阵里有成对相等的奇异值,1/(s_j² − s_i²) 除以 0;tenferro 默认的展宽规则给出有限、正确的梯度;JAX 里用 custom_jvp 补上同样的规则,以及 jit 把 s_j**2 - s_i**2 融合成 FMA、梯度变成 1e120 的坑。
  3. SVD 的输出:jnp.linalg.svd 默认返回满矩阵,tenferro 默认是薄的;对 SVD、QR 的输出直接切片再求导会报 slice input requires constant exact or upper-bound extents,先 reshape、用掩码或 take_axis 都能绕开;k 由数据决定时两边的写法;faer 给出恰好为 −0 的奇异值,sqrt(s) 的梯度是 NaN。
flowchart TB
  t["局部张量 T"] --> m["transpose reshape"]
  m --> svd["SVD"]
  svd --> sign["符号 规范"]
  svd --> deg["简并 求导规则"]
  svd --> cut["截断 前 k 个"]
  cut --> sq["sqrt s"]
  sq --> c["收缩成 T'"]
  c --> t

💬 关联讨论

发起讨论
加载中...