summaryrefslogtreecommitdiff
path: root/assets/hw/hw_mvp_explainer.html
blob: 41146482ded951fc09e9178cb0ad16b9371ea5d0 (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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<title>无时钟模拟学习板 — 结构图解(给不懂硬件的人)</title>
<style>
  body { font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif; margin: 0;
         background: #fafbfd; color: #222; line-height: 1.65; }
  .wrap { max-width: 1080px; margin: 0 auto; padding: 28px 20px 80px; }
  h1 { color: #13294B; font-size: 26px; border-bottom: 3px solid #E84A27; padding-bottom: 8px; }
  h2 { color: #13294B; font-size: 20px; margin-top: 2.2em; }
  .lead { font-size: 16.5px; color: #444; }
  .lead b { color: #E84A27; }
  svg { width: 100%; height: auto; background: white; border: 1px solid #dde3ec; border-radius: 10px; }
  .hint { text-align: center; color: #888; font-size: 13px; margin-top: 6px; }
  [data-tip] { cursor: help; }
  [data-tip]:hover { filter: brightness(1.15) drop-shadow(0 0 4px rgba(232,74,39,.45)); }
  #tip { position: fixed; display: none; max-width: 340px; background: #13294B; color: #fff;
         padding: 10px 13px; border-radius: 8px; font-size: 13.5px; line-height: 1.5; z-index: 10;
         box-shadow: 0 4px 18px rgba(0,0,0,.25); pointer-events: none; }
  #tip b { color: #FFB894; }
  table { border-collapse: collapse; width: 100%; font-size: 14.5px; }
  th, td { border: 1px solid #ccd4e0; padding: 8px 12px; text-align: left; }
  th { background: #13294B; color: white; }
  tr:nth-child(even) { background: #f0f3f8; }
  .steps li { margin: 10px 0; }
  .steps b { color: #13294B; }
  .chip { display: inline-block; background: #eef1f7; border: 1px solid #ccd4e0; border-radius: 14px;
          padding: 3px 12px; margin: 3px 4px; font-size: 13.5px; }
  .chip b { color: #E84A27; }
  .note { background: #fff7f3; border-left: 4px solid #E84A27; padding: 10px 16px; border-radius: 0 8px 8px 0;
          font-size: 14.5px; }
</style>
</head>
<body>
<div class="wrap">

<h1>无时钟模拟学习板:它长什么样、为什么能学习</h1>
<p class="lead">
一句话:这块板子上<b>同时活着两份一模一样的电路</b>——一份自由地"回答问题"(FREE),一份的输出被轻轻拉向正确答案(NUDGED)。
每个"权重"是一颗电容,它不停地比较自己在两份电路里的受力差异、朝着缩小差异的方向缓慢充放电。
<b>没有处理器、没有时钟、没有数模转换——物理本身在做训练。</b>
把鼠标悬停在图里任何东西上看它是什么。
</p>

<svg viewBox="0 0 1200 620" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <marker id="arr" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">
      <path d="M0,0 L7,3 L0,6 Z" fill="#2E8B57"/>
    </marker>
  </defs>

  <!-- ================== FREE replica ================== -->
  <text x="235" y="46" text-anchor="middle" font-size="21" font-weight="bold" fill="#13294B">FREE 网络(自由态)</text>
  <text x="235" y="70" text-anchor="middle" font-size="13" fill="#888">自然弛豫,输出 = 网络自己的回答 ŷ</text>
  <g data-tip="<b>FREE 网络</b>:真实的物理电路。输入电压加上后,电流按电阻自然流动,节点电压在微秒级&quot;稳定下来&quot;(settle)——这个稳定过程就是 ML 里的前向传播,不需要任何计算指令。">
    <rect x="60" y="85" width="350" height="420" rx="14" fill="#f4f6fa" stroke="#13294B" stroke-width="2"/>
  </g>
  <!-- nodes -->
  <g data-tip="<b>输入节点</b>:一个电压源把输入 x 设成固定电压(比如 0.3V 代表某个特征值)。两份网络收到完全相同的输入。">
    <circle cx="120" cy="170" r="22" fill="white" stroke="#13294B" stroke-width="2.5"/>
    <text x="120" y="176" text-anchor="middle" font-size="15" fill="#13294B" font-style="italic">x₁</text>
    <circle cx="120" cy="420" r="22" fill="white" stroke="#13294B" stroke-width="2.5"/>
    <text x="120" y="426" text-anchor="middle" font-size="15" fill="#13294B" font-style="italic">x₂</text>
  </g>
  <g data-tip="<b>隐藏节点</b>:中间节点的电压就是 ML 里的&quot;隐层激活值&quot;。它不被任何人直接设定——是电流平衡自己找到的值。">
    <circle cx="245" cy="295" r="22" fill="white" stroke="#13294B" stroke-width="2.5"/>
    <text x="245" y="301" text-anchor="middle" font-size="15" fill="#13294B" font-style="italic">h</text>
  </g>
  <g data-tip="<b>输出节点 ŷ</b>:FREE 网络稳定后,这里的电压就是网络的预测。用示波器可以读它,但读数不参与任何学习回路。">
    <circle cx="360" cy="295" r="22" fill="white" stroke="#13294B" stroke-width="2.5"/>
    <text x="360" y="301" text-anchor="middle" font-size="15" fill="#13294B" font-style="italic">ŷ</text>
  </g>
  <!-- edges with MOSFET boxes -->
  <g data-tip="<b>可调边(一个 MOSFET 晶体管)</b>:连接两个节点的&quot;电阻&quot;,导电能力由旁边绿色电容的电压控制——<b>这就是一个权重</b>。板上每条这样的边配一个孪生兄弟(右边网络里位置相同的边),两兄弟永远共享同一颗权重电容。">
    <line x1="140" y1="182" x2="228" y2="285" stroke="#13294B" stroke-width="2.5"/>
    <rect x="168" y="216" width="30" height="18" fill="#dfe7f3" stroke="#13294B" stroke-width="1.5"/>
    <line x1="140" y1="408" x2="228" y2="305" stroke="#13294B" stroke-width="2.5"/>
    <rect x="168" y="342" width="30" height="18" fill="#dfe7f3" stroke="#13294B" stroke-width="1.5"/>
    <line x1="267" y1="295" x2="338" y2="295" stroke="#13294B" stroke-width="2.5"/>
    <rect x="287" y="286" width="30" height="18" fill="#dfe7f3" stroke="#13294B" stroke-width="1.5"/>
    <line x1="139" y1="152" x2="341" y2="282" stroke="#13294B" stroke-width="2.5"/>
    <rect x="225" y="196" width="30" height="18" fill="#dfe7f3" stroke="#13294B" stroke-width="1.5"/>
  </g>

  <!-- ================== NUDGED replica ================== -->
  <text x="875" y="46" text-anchor="middle" font-size="21" font-weight="bold" fill="#E84A27">NUDGED 网络(微推态)</text>
  <text x="875" y="70" text-anchor="middle" font-size="13" fill="#888">同样的电路 + 输出被轻拉向目标 y*</text>
  <g data-tip="<b>NUDGED 网络</b>:FREE 的物理复制品(同样的布线、配对的晶体管),唯一区别:输出端多了一个小电流,把它往正确答案方向轻推。整个网络因此微微变形——每条边感受到的电压差都和 FREE 侧略有不同,<b>这个差异里藏着梯度</b>。">
    <rect x="700" y="85" width="350" height="420" rx="14" fill="#fff6f2" stroke="#E84A27" stroke-width="2"/>
  </g>
  <g>
    <circle cx="760" cy="170" r="22" fill="white" stroke="#E84A27" stroke-width="2.5"/>
    <text x="760" y="176" text-anchor="middle" font-size="15" fill="#E84A27" font-style="italic">x₁</text>
    <circle cx="760" cy="420" r="22" fill="white" stroke="#E84A27" stroke-width="2.5"/>
    <text x="760" y="426" text-anchor="middle" font-size="15" fill="#E84A27" font-style="italic">x₂</text>
    <circle cx="885" cy="295" r="22" fill="white" stroke="#E84A27" stroke-width="2.5"/>
    <text x="885" y="301" text-anchor="middle" font-size="15" fill="#E84A27" font-style="italic">h</text>
  </g>
  <g data-tip="<b>被推的输出 ŷβ</b>:因为 OTA 的小电流,这里的电压介于&quot;网络自己的回答&quot;和&quot;正确答案&quot;之间——偏向正确答案一点点(推力强度 = β)。">
    <circle cx="1000" cy="295" r="22" fill="white" stroke="#E84A27" stroke-width="3"/>
    <text x="1000" y="301" text-anchor="middle" font-size="14" fill="#E84A27" font-style="italic">ŷβ</text>
  </g>
  <g data-tip="<b>可调边(孪生 MOSFET)</b>:和左边网络位置对应的边共享同一颗权重电容——所以两份网络的&quot;权重&quot;永远相同,差别只来自输出端的推力。">
    <line x1="780" y1="182" x2="868" y2="285" stroke="#E84A27" stroke-width="2.5"/>
    <rect x="808" y="216" width="30" height="18" fill="#fde4da" stroke="#E84A27" stroke-width="1.5"/>
    <line x1="780" y1="408" x2="868" y2="305" stroke="#E84A27" stroke-width="2.5"/>
    <rect x="808" y="342" width="30" height="18" fill="#fde4da" stroke="#E84A27" stroke-width="1.5"/>
    <line x1="907" y1="295" x2="978" y2="295" stroke="#E84A27" stroke-width="2.5"/>
    <rect x="927" y="286" width="30" height="18" fill="#fde4da" stroke="#E84A27" stroke-width="1.5"/>
    <line x1="779" y1="152" x2="981" y2="282" stroke="#E84A27" stroke-width="2.5"/>
    <rect x="865" y="196" width="30" height="18" fill="#fde4da" stroke="#E84A27" stroke-width="1.5"/>
  </g>
  <!-- target + OTA -->
  <g data-tip="<b>目标 y*</b>:正确答案,也用一个电压表示。只有 NUDGED 网络&quot;知道&quot;它——通过 OTA 的推力间接感受到。">
    <circle cx="1130" cy="295" r="22" fill="#eaf7ef" stroke="#2E8B57" stroke-width="2.5"/>
    <text x="1130" y="301" text-anchor="middle" font-size="15" fill="#2E8B57" font-style="italic">y*</text>
  </g>
  <g data-tip="<b>OTA(跨导放大器,约 $1.7 一颗)</b>:持续测量&quot;输出与目标的差距&quot;,按差距大小注入一股小电流把输出往目标推。推力系数就是 EP 里的 β。它相当于告诉网络:&quot;往这边一点点&quot;——但从不告诉任何一条边该怎么改。">
    <path d="M 1060,272 L 1060,318 L 1096,295 Z" fill="#eaf7ef" stroke="#2E8B57" stroke-width="2"/>
    <line x1="1022" y1="295" x2="1058" y2="295" stroke="#2E8B57" stroke-width="2" marker-end="url(#arr)"/>
    <text x="1078" y="345" text-anchor="middle" font-size="12.5" fill="#2E8B57">电流微推 (β)</text>
  </g>

  <!-- ================== weight caps + contrast cells ================== -->
  <text x="555" y="115" text-anchor="middle" font-size="15" font-weight="bold" fill="#2E8B57">共享权重 + 学习单元(每条边一套)</text>
  <g data-tip="<b>对比单元 + 权重电容(紫框)</b>:每条边配一套。它同时量到这条边在 FREE 侧的电压差 Δv 和在 NUDGED 侧的电压差 Δvβ,比较两者(精确版算 Δvβ²−Δv²,便宜版只取正负号),把成比例的电流灌进绿色电容。<b>电容电压 = 权重</b>,它同时控制左右两个孪生 MOSFET 的导电性。这就是一次&quot;梯度更新&quot;——纯模拟、纯局部、连续发生。">
    <g id="cells"></g>
  </g>

  <!-- learn/freeze switch -->
  <g data-tip="<b>学习/冻结开关(机械拨杆)</b>:断开所有电容的充电电流 = 冻结权重(相当于 model.eval())。故意用机械开关而不是芯片——保住&quot;学习回路里没有任何数字器件&quot;的主张。">
    <rect x="505" y="530" width="100" height="34" rx="8" fill="#eef1f7" stroke="#555" stroke-width="1.5"/>
    <text x="555" y="552" text-anchor="middle" font-size="12.5" fill="#333">learn / freeze</text>
  </g>
  <!-- scope -->
  <g data-tip="<b>示波器(只看不摸)</b>:观察节点电压用,不在任何反馈或学习回路里。这是&quot;完全模拟&quot;主张允许的唯一仪器。">
    <rect x="1075" y="500" width="90" height="60" rx="8" fill="white" stroke="#888" stroke-width="1.5"/>
    <path d="M 1085,540 q 12,-24 24,0 t 24,0 t 24,0" fill="none" stroke="#2E8B57" stroke-width="2"/>
    <text x="1120" y="516" text-anchor="middle" font-size="11" fill="#888">示波器</text>
  </g>
</svg>
<p class="hint">悬停任何元素查看解释 · 紫框 = 学习单元(对比 + 电容)· 绿 = 权重/目标相关 · 蓝 = FREE · 橙 = NUDGED</p>

<h2>ML ↔ 电路 词典</h2>
<table>
  <tr><th>机器学习里的</th><th>板子上的</th><th>备注</th></tr>
  <tr><td>激活值 z</td><td>节点电压</td><td>没人"计算"它——电流平衡自己找到它</td></tr>
  <tr><td>权重 w</td><td>一颗电容的电压(控制 MOSFET 导电性)</td><td>非易失升级版 = FeFET(Zhu 组的方向)</td></tr>
  <tr><td>前向传播</td><td>RC 自然弛豫(~微秒)</td><td>免费,且天然并行</td></tr>
  <tr><td>损失的输出梯度</td><td>OTA 电流 = g·(ŷ − y*)</td><td>只在输出端,任何学习规则都需要这个</td></tr>
  <tr><td>反向传播</td><td><b>不存在</b></td><td>被"双生网络 + 局部对比"替代</td></tr>
  <tr><td>每个权重的梯度</td><td>该边两侧压降的平方差 Δvβ² − Δv²</td><td>纯局部量——只需要摸自己这条边</td></tr>
  <tr><td>optimizer.step()</td><td>电容充/放电(~毫秒-秒)</td><td>学习率 = 充电电流大小</td></tr>
  <tr><td>训练循环 / 时钟</td><td><b>不存在</b></td><td>两态同时物理存在 + 快慢时间尺度分离</td></tr>
</table>

<h2>一次"学习"是怎么发生的(其实是连续的,这里人为分步)</h2>
<ol class="steps">
  <li><b>摆题:</b>手动开关把输入 x 设到两份网络上,把目标 y* 设到 OTA 上。</li>
  <li><b>瞬间稳定(微秒级):</b>两份网络各自弛豫。FREE 的输出是网络自己的回答;NUDGED 的输出被 OTA 拉向正确答案一点点。</li>
  <li><b>形变传播:</b>输出端被拉动后,NUDGED 网络<b>全身</b>的电压分布都微微变形——离输出越远变形越小,但每条边都感受得到。这就是"credit assignment 由物理完成"。</li>
  <li><b>逐边对比(连续进行):</b>每条边的学习单元比较自己在两侧的压降,差异越大充电越快。方向恰好是能量下降的方向——数学上等价于梯度下降。</li>
  <li><b>慢速累积(毫秒-秒级):</b>权重电容比节点慢 100–1000 倍,所以在权重看来网络永远是"稳定的"——<b>这个时间尺度差就是时钟的替代品</b>。</li>
  <li><b>换题:</b>手动换下一组输入/目标。重复。loss 就这样降下去(Penn 的 32 边版本已经在物理上学会了 XOR 和非线性回归)。</li>
</ol>

<h2>造价阶梯</h2>
<p>
<span class="chip">① 单边标定件 <b>$70–130</b>(先证明物理)</span>
<span class="chip">② 8 边网络 <b>$170–300</b> ← 推荐 MVP</span>
<span class="chip">③ 32 边 <b>$450–900</b>(复现 Penn 规模)</span>
<span class="chip">④ 注意力微单元 <b>+$100–250</b></span>
<span class="chip">⑤ CIM transformer block(合作方阶段)</span>
</p>

<div class="note">
<b>诚实边界</b>(也是给合作者的说法):这块板子验证的是<b>物理学习原语</b>——无时钟双生 EP、局部模拟更新、真电流 nudge vs 电压钳制的对比——
<b>不是</b> transformer 本身。它和我们 GPU 侧共享同一条定律:nudge 幅度 vs 噪声的信噪比曲线(我们在 fp32 里测过的 β 定律,这块板会在物理里再测一遍)。
</div>

</div>
<div id="tip"></div>
<script>
// draw 4 contrast-cell/cap pairs + dashed pairing lines
const svg = document.querySelector('svg');
const cells = document.getElementById('cells');
const pairs = [
  {y: 205, fL: [183, 225], fR: [808, 225]},
  {y: 285, fL: [240, 205], fR: [880, 205]},
  {y: 365, fL: [302, 295], fR: [942, 295]},
  {y: 445, fL: [183, 351], fR: [808, 351]},
];
let inner = '';
pairs.forEach((p, i) => {
  inner += `
  <rect x="480" y="${p.y-32}" width="150" height="64" rx="10" fill="#faf7ff" stroke="#7B4FB6" stroke-width="2"/>
  <text x="555" y="${p.y-12}" text-anchor="middle" font-size="11.5" fill="#7B4FB6">对比: Δv²β − Δv²</text>
  <line x1="543" y1="${p.y+2}" x2="543" y2="${p.y+20}" stroke="#2E8B57" stroke-width="2"/>
  <line x1="537" y1="${p.y+6}" x2="549" y2="${p.y+6}" stroke="#2E8B57" stroke-width="3"/>
  <line x1="537" y1="${p.y+13}" x2="549" y2="${p.y+13}" stroke="#2E8B57" stroke-width="3"/>
  <text x="585" y="${p.y+16}" text-anchor="middle" font-size="11" fill="#2E8B57">C_w${i+1}</text>
  <line x1="${p.fL[0]+15}" y1="${p.fL[1]+9}" x2="480" y2="${p.y}" stroke="#7B4FB6" stroke-width="1.3" stroke-dasharray="5,4" opacity="0.75"/>
  <line x1="${p.fR[0]+15}" y1="${p.fR[1]+9}" x2="630" y2="${p.y}" stroke="#7B4FB6" stroke-width="1.3" stroke-dasharray="5,4" opacity="0.75"/>`;
});
cells.innerHTML = inner;

const tip = document.getElementById('tip');
document.querySelectorAll('[data-tip]').forEach(el => {
  el.addEventListener('mousemove', e => {
    tip.innerHTML = el.getAttribute('data-tip');
    tip.style.display = 'block';
    tip.style.left = Math.min(e.clientX + 16, window.innerWidth - 360) + 'px';
    tip.style.top = (e.clientY + 18) + 'px';
  });
  el.addEventListener('mouseleave', () => tip.style.display = 'none');
});
</script>
</body>
</html>