summaryrefslogtreecommitdiff
path: root/docs/audit/EP_READOUT_FLOOR_AUDIT.md
blob: cc7a071ad3bb12e1cd899be2c2df03cfd98693c4 (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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# 对比读出地板: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.

## 给 Ben 的信中"请教 K"段(预写, 用户 08-03 决定: 跑完 135M 后一并发出并征询他的意见)
> One question I would rather ask you than decide on my own. In our layered setup the free phase is a
> single forward pass, as it is for PCNs in your ImageNet paper, so the only thing that iterates is the
> nudged equilibrium. We transmit the nudge down the whole stack analytically in one round, rather than
> updating layers asynchronously, and with that transmission the number of rounds turns out not to
> matter: at a fixed checkpoint the cosine to the backprop gradient is 0.9999 at one round, and over a
> paired six thousand step comparison the final loss at one, two, and three rounds agrees to three parts
> in a hundred thousand, against a seed to seed spread two orders of magnitude larger. So in this
> architecture the equilibrium iteration appears to do no measurable work, and one round is the adjoint
> up to a correction of order beta that sits only in the parameter Jacobian.
>
> Your sensitivity result gives K at least four, with the optimum near the depth of the network, which
> makes sense for asynchronous updates where a round moves information about one layer. I do not think
> our round and your round are the same unit, so I am reluctant to read our result as contradicting
> yours, and equally reluctant to quietly report one round as though the iteration count were a free
> parameter. How would you report this? Is the round count a meaningful axis at all for an architecture
> whose free phase is exact by construction, or is the honest framing that the contribution sits in the
> locality of the update and the finite beta behavior rather than in the equilibrium computation itself?
> Dropping to one round would take our training cost from 2.7 times backprop to about 1.6, which is
> worth having, but not at the price of describing the method inaccurately.