summaryrefslogtreecommitdiff
path: root/docs/audit/EP_READOUT_FLOOR_AUDIT.md
blob: 7786b873d0f1b3628dbcd09e3ee32926830fa784 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# 对比读出地板:EP / coupled-learning 公开实现审计
*2026-08-02 · 判据 = "对比量是否由两个量级相近的大状态相减取回,且在 fp32 中除以小 β"*

## 失效律(可引用的定量结果)
定义 **r = ‖s_nudged − s_free‖ / ‖s_free‖**(位移-激活比)。fp32 估计器的相对误差
**≈ 0.3–3 × eps32 / r**,在 r = 1e-1…1e-8 上验证成立。阈值:
r≈1e-5 → 1–3% 梯度误差;r≈1e-6 → ~30%;r≈1e-7 → 梯度即噪声。

## 结论一句话
**这个模式近乎普遍(可访问的 ~20 个 EP 实现中 13 个命中),但整个领域目前是"侥幸安全"的**:
物理侧的仿真跑在 numpy/MATLAB 的 float64 上,ML 侧则因为偏差抑制的需要普遍用 β ≥ 0.1。
让 EP 得以 scale 的那些技术(对称估计器、全息 EP 允许大 β)恰好也把它推离了精度悬崖。
**没有一个仓库对 r 设断言、对小 β 告警、或用 fp64 累加对比量** —— 安全边际处处存在、处处无人看守。
两个结构性趋势正在侵蚀它:向 JAX / PyTorch-GPU 迁移(默认 fp32;**本次调查中无一仓库启用
`jax_enable_x64`**),以及向更深模型推进。

## Tier 1 — 与我们完全同型且设置危险(唯一一个)
**`Rasmuskh/dualprop_icml_2024`**(ICML 2024, "Two Tales of Single-Phase CHL")@ `7b2595b`
本人亲自 clone 核对,非转述:
- `src/models.py:221-223` 状态由"大 + 小"形成:`splus_k = act(ff + (1-α)·fb)`、`sminus_k = act(ff − α·fb)`
- `src/models.py:204` 再由相减取回:`delta = splus[i] - sminus[i]`
- `src/models.py:229-231` **输出层无激活**:`splus_out − sminus_out = −fb` 是精确代数恒等,
  而 `fb` 就在第 229 行以独立张量存在 → **我们的修复在输出层可直接照搬(1 行)**
- `get_phi` 双线性(`:134`),故 `J` 已是线性对比形式:算法正确, 只是喂进了被舍坏的 `delta`
- 出厂配置为全场最差:`bash_scripts/cifar10/dp_lagr_alpha_0.0_beta_0.01.sh:5` `beta=0.01`、
  `nudged_passes=16`、所有脚本 `--dtype float32 --param-dtype float32`、模型 VGG16
- ⚠️ 我方修正:隐藏层的差经过 `act`,`splus−sminus ≠ fb`,**不是逐字替换**;那里的正确处方是
  把逐元素的加法与相减放到更高精度(廉价),而非改算法。对外沟通须区分这两层。

## Tier 2 — 结构上脆弱、当前设置安全(pattern b:两次独立弛豫相减除以 β)
Scellier 原版 `bscellier/Towards-a-Biologically-Plausible-Backprop`(`model.py:184-185`,β=0.5/1.0,
README 明写 `floatX=float32`);`Laborieux-Axel/Equilibrium-Propagation`(`model_utils.py:192` 等,
β=0.01–0.5,实测最坏 ≤3e-4;`main.py:87` 有一行被注释掉的 fp64 开关);**`rain-neuromorphics/
energy-based-learning`(Scellier@Rain,`training/sgd.py:357`,支撑三篇论文,全程 fp32)**;
`ernoult/updatesEPgradientsBPTT`、`ernoult/continualEP`、`NeuroCompLab-psu/ScalableEPInterErr`、
`smonsays/equilibrium-propagation`、`zeligism/eqprop`、`alexgower/OIM-EP`(默认 β=0.01,且是唯一在
训练路径上带 fp64 开关的仓库)、`lubo9595/cSB-EP`、`chaganbajala/XY_EqProp`、
`maguzj/coupled_learning`(JAX 路径 fp32 且库默认 `eta=0.001`)。
**这些无法套用我们的修复**:两个状态来自独立弛豫,位移从不以单独张量存在;它们的护身符只有大 β。

**附带代数发现(可无偿转赠)**:把对比量写成外积之差(`s₂ᵀs₂ − s₁ᵀs₁`)比先做状态差再收缩
(`aᵀ(b₂−b₁)`)**精度差约 11×**,因为抵消误差会穿过归约累积。`QUVA-Lab/spiking-eqprop` 默认用了
好的因子化形式(`eq_prop.py:155`),仅在 `bidirectional='full'` 分支退化。

## PARTIAL / CLEAN 要点
- `mawatfa/ebana`:`(nudge)**2 − (free)**2` 且 **β=2e-9**,全靠 numpy 默认 fp64 救命 —— 任何人把它
  移植到 torch/GPU 当天就炸。已验证教科书式 `(a−b)(a+b)` 重写**救不回来**(fp32 下 4.5e-2 vs 4.0e-2),
  因为损伤已在两个被舍入的操作数里 —— 与我们"Sterbenz 精确相减忠实返回已损坏的 d"同因。
- **全息 EP 不是结构免疫**(修正我方先前假设):complex-step 免疫是因为大实部从不进入虚部字长;
  全息 EP 是 N 点 Cauchy 求积,β 无关项靠 `Σe^{−2πin/N}=0` 抵消,仍是大数相减,同样有 ~ε/β 放大。
  它消的是 O(β²) 截断偏差, 不是舍入。`Laborieux-Axel/holomorphic_eqprop` 与 `generalized-holo-ep`
  均未启用 JAX x64(complex64),后者 `--half_prec` 下弛豫跑 float16。
- 结构免疫:`jlaydevant/Ising-Machine-EqProp`(二值 {0,1} 状态,差为精确 0/±1);`locuslab/deq`
  (全解析 VJP,无有限差分);`victorlopezpastor/self_learning_machines`(complex128);
  `ClaraWanjura/QuantumEP`(同时提供有限 nudge 与精确线性响应伴随路径,可作模板)。
- UPenn 物理学习仿真(Dillavou / Stern / Guzmán / Andrejevic)继承 float64,**但真正的地板是求解器
  容差 1e-8/1e-6 而非 dtype**,实际余量 5–7 位而非 16 位;救它们的其实是大 η。
- `maguzj/learning_circuit`(`coupled_learning` 的后继)已把 CL 更新限制在 fp64 numpy/scipy = 事实上
  的修复。

## UNKNOWN(无公开代码,明确标注而非猜测)
**KHS PCN-on-ImageNet (2606.03584) 未放代码**。结构上最值得关注:其 PCN 单相形式无两梯度相减,
但 `F_PCN` 由预测误差 `ε_l = h_l − f(h_{l−1})` 构成 = pattern (a),而 Rain 自家框架正是在
`model/forward/interaction.py:50` 这么算的。论文 β=0.02(VGG5)/0.05(VGG10 ImageNet),
**但敏感性扫描一直做到 β=2e-4** —— 那一端余量比标称设置薄 250×。
其余无公开代码(逐一搜索确认):Menachem Stern 全部四篇、Anisetti/Schwarz、EqSpike、
Hughes et al. Optica 2018、Kendall 2006.01981、2503.22810、2602.03670、StochEP、忆阻器 crossbar。
`guillaumepourcel/Lagrangian-Equilibrium-Propagation` 解析得到但为空占位仓库,勿引为可用。

## 诚实的负面结果
子代理测了 Laborieux 的 **MLP**:r **不**随深度衰减(随机初始化下 4 层后反而增长)。
干净的 63× 衰减是**卷积架构**的结果。**不得**把"深度驱动 r 塌陷"当作普遍律来主张。

## 该通知谁(优先序)
1. **Rasmus Kjær Høier / Christopher Zach**(dualprop):唯一在 VGG16 × β=0.01 × fp32 下出厂即
   带此 bug 的项目,输出层可 1 行修复。值得发一封信。
2. **Benjamin Scellier**:`training/sgd.py:357` 是三篇论文底下的估计器且小 nudge 时无防护;且未发布
   的 ImageNet PCN 工作公布的 β 扫描下探到 2e-4。他已是开放联系人,且与我们的发现直接相关。
3. **Axel Laborieux**:领域参照实现,被逐字 fork(NeuroCompLab、alexgower 均带同一行)。一句
   "fp32 下 β ≲ 1e-4 不安全"的文档说明可以给整棵下游树打疫苗。
4. **Marcelo Guzmán**:`coupled_learning` 的 JAX 路径 fp32 + 库默认 eta=0.001;其后继仓库
   `learning_circuit` 已修,通知内容只是"backport"。

## 给我们自己写作的可迁移教训
**同一个算术,两种处方**。位移以独立张量存在时(dualprop 与我们),修复免费且彻底;两个状态来自
独立弛豫时(其余所有人),任何重写都救不回来(已验证),只剩 fp64 累加对比量或给 β 设下限两条路。
这一点必须写清楚,否则读者会望文生义地按"灾难性抵消"去找数值稳定的重写形式。

---

## 关键人际连接(审计本身未点出,本人核对)
**`Rasmuskh` = Rasmus Kjær Høier**(仓库 commit 署名 `Rasmus Kjær Høier`,README 指向 ICML 2024
"Two Tales of Single-Phase Contrastive Hebbian Learning", arXiv:2402.08573),**即 KHS ImageNet PCN
论文 (arXiv:2606.03584, Kerjan/Høier/Scellier, 2026-06-02) 的第二作者**。
论文已公开发表;**代码未发布**(arXiv 页与检索均无仓库链接,2026-08-02 复核)。

⟹ **策略推论**:一封发给 Ben Scellier 的信即可同时覆盖三个目标,且走已开启的暖渠道,无需三封冷邮件:
(1) 他自己 Rain 的 `energy-based-learning/training/sgd.py:357`;(2) 合著的 ImageNet PCN(β 标称 0.05,
敏感性扫描下探 2e-4, 余量薄 250×);(3) 合作者 Høier 的 dualprop(唯一出厂即危险者, 输出层可 1 行修复)。
时机:等 135M 修复版完赛后一并发出(结果本身是这封信的由头)。

## 给 Ben 的信中技术段(预写, 不依赖 135M 结果)
> While tracing a width dependent quality loss in our own language model runs, we found the cause was
> arithmetic rather than the learning rule. We form the nudged state as z = o + d and then read the
> contrast as the squared norm of z minus o. Those agree algebraically and not in float32: forming z
> rounds away every component of d below machine epsilon times the local activation, and the later
> subtraction returns the damaged copy with no error signal. Wider models push more layers below that
> threshold, so the loss grew with width and imitated a scaling wall. Reading the contrast from the
> stored d instead, which has the same derivative, costs nothing and removed the effect completely.
>
> We then checked whether the pattern appears elsewhere, and it is common. The relative error of a
> float32 contrast estimator follows roughly epsilon over r, where r is the displacement to activation
> ratio, so r near 1e-5 costs a few percent and r near 1e-7 leaves noise. Most published work is
> protected by a large nudge or by float64 in numpy, and the margin is real but undocumented: none of
> the implementations we read assert on r or accumulate the contrast in double precision. Two specific
> notes, offered privately and only because they are close to you. The estimator in
> energy-based-learning at training/sgd.py:357 is unguarded at small nudging, and the beta sweep in the
> ImageNet PCN paper reaches 2e-4, where the margin is a couple of hundred times thinner than at the
> headline setting. Separately, Rasmus's dualprop code forms the states as act(ff plus or minus fb) and
> then recovers the difference by subtraction, in float32 at beta 0.01 on VGG16; at the output layer
> there is no activation in between, so the difference is exactly fb and the retained tensor can be
> substituted directly. In the hidden layers the difference passes through the activation, so the
> remedy there is to form it elementwise in higher precision rather than to substitute.
>
> A standalone checker is at [tools/contrast_floor_check.py], and it runs against any implementation.