Rust in Everything
基于 tenferro-runtime 0.8.0

常见坑

每一章从一个 JAX(或 PyTorch)的习惯出发:先给 JAX 那边的写法和输出,再把它照搬到 tenferro,给出报错原文或静默的错误结果,然后找出处、给修法和检查办法。输出是 M2 Max、macOS 27、rustc 1.99.0 上 JAX 0.12.0.1(CPU)、PyTorch 2.15.0 和 tenferro-rs 0.8.0 的原文。机制在 tenferro 卷的常见坑里已经讲过的,这里只讲从 JAX 走过去为什么会撞上,然后链过去。

有两条是 JAX 一侧的坑(8、20):搬程序时要先有一份可信的 JAX 参考结果,这两条会让参考本身不对。最危险的是不报错的那几类:布局和批维(1、2)、下标和 scatter(4、5)、整数系数(7)、被替换的输入(10)、二阶导(12)、复数(17、18)、有限差分(29)。

数组与 dtype

  1. 同一段数据 reshape 出来的矩阵不一样:tenferro 的 reshape 按列主序,相当于 order="F"。
  2. 照搬 JAX 的批维形状,静默算成别的批:批维在最右边,[4,2,3] 被当成 3 批 4×2。
  3. 整数加 f32 变成了 f64:vec![1, 2] 是 I32,I32 加 F32 提升成 F64。
  4. x[-1] 取到的是第一个元素:负下标不回绕,直接夹到 0。
  5. scatter 把更新加了上去:tenferro 的 scatter 是累加的,越界的更新也夹到边上。
  6. real value must be finite when casting to i32:NaN、无穷和超范围的浮点转整数,到 run 才报错。
  7. 整数乘 0.5 原样返回:scale_real 作用在整数上,系数先四舍五入。
  8. JAX 一侧静默降成 float32,对不上:没开 x64 的 JAX 参考差到 1e-7。

变换与 eager

  1. binding shape mismatch for concrete-shape placeholder:jit 换形状会重编译,tenferro 的具体形状占位符直接拒绝。
  2. 切向量、种子、常量都能被 run 的输入替换:传齐所有输入时按位置对应,顺序错了不报错。
  3. 同时要值和梯度:has no runtime input binding:compile_many 没有带输入规格的版本。
  4. 经过 SVD 的 grad(grad) 静默偏了约 6%:反向套反向撞上展宽的 F,用 jvp(grad)。
  5. no_grad 下 backward 成功,却没有梯度:PyTorch 会报错,tenferro 返回空的梯度集。
  6. with_eager_session 返回元组:E0283:闭包的错误类型推不出来,编译器的提示也编不过。
  7. 每次 backward 都一样慢:eager 每次都重建求导图,深图改用 traced 或分段。

复数

  1. placeholder semantic input 2 has no runtime input binding:vjp_program 的种子排在最后,没有默认值。
  2. 复数梯度下降沿虚轴发散:JAX 的梯度要取共轭,tenferro 的不用,照搬就多取了一次。
  3. 复数输出求导不报错,给的是实部的梯度:忘了取模的损失在 JAX、PyTorch 里报错,在 tenferro 里静默下降到负无穷。
  4. unsupported dtype conversion from C64 to F64:取实部要用 cast,convert 只做无损转换。

SVD 与线性代数

  1. JAX 自带 SVD 的梯度从第 5 步起是 NaN:重复的奇异值;自己写规则时还会撞上 jit 的 FMA 对角线。
  2. slice input requires constant exact or upper-bound extents:对 SVD、QR 的输出切片后求导失败,上游 bug。
  3. sqrt(s) 的梯度是 NaN:faer 给出精确为零的奇异值,derivative_eps 管不到。
  4. lu_factor_solve: rank mismatch: expected 2, actual 1:solve 的右端必须是矩阵。
  5. 奇异向量和特征向量的符号和 JAX 不同:拿存下来的 JAX 向量直接比会失败,比不依赖符号的量。

求导的代价、计时与比对

  1. 整张图求导,几步之后就停不下来:值被重复使用时求导变换按指数增长,上游问题;分段。
  2. checkpoint 之后栈溢出:检查点链每步膨胀,5 步就在丢弃时压爆主线程栈,上游问题。
  3. 换了线程数,末位不一样:同一宽度逐位可重复,换宽度就变;比对时固定宽度。
  4. 第一次跑慢、满载时慢二十倍:新二进制的第一次启动、超订的多线程 engine。
  5. 有限差分的比热看上去合理,其实差 1.3%:差分步长跨过了截断处奇异值的交叉点,换两个步长核对。
flowchart TB
  q{"碰到了什么"}
  q -->|报错| err{"在哪一步"}
  err -->|"编译"| c["14"]
  err -->|"prepare 或 run"| r["6 9 11 16"]
  err -->|"求导"| d["21 23"]
  err -->|"转换"| v["19"]
  q -->|结果不对| w{"哪里不对"}
  w -->|"数值错位"| layout["1 2 4 5 7"]
  w -->|"梯度"| g["10 12 13 17 18 22"]
  w -->|"NaN"| n["20 22"]
  w -->|"和 JAX 差末位"| bits["3 8 24 27"]
  w -->|"比热"| fd["29"]
  q -->|慢或停不下来| s["15 25 26 28"]

💬 关联讨论

发起讨论
加载中...