diff options
| author | Yuren Hao <yurenh2@illinois.edu> | 2026-07-15 10:32:26 -0500 |
|---|---|---|
| committer | Yuren Hao <yurenh2@illinois.edu> | 2026-07-15 10:32:26 -0500 |
| commit | a6d88a18a4bfd46ab0dd487730b72dcbc3b8b5b8 (patch) | |
| tree | ad6a724c445958c8e5ddca1906fdd9bc63a764a2 /assets/hw/hw_mvp_explainer.html | |
| parent | 82da86dbd49a093a24e0827331667e8e9e6217df (diff) | |
assets/: organize into decks|figs|hw|refs|misc; scripts + gitignore repointed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FAPDWQ49M5Ye3NpTndTpn
Diffstat (limited to 'assets/hw/hw_mvp_explainer.html')
| -rw-r--r-- | assets/hw/hw_mvp_explainer.html | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/assets/hw/hw_mvp_explainer.html b/assets/hw/hw_mvp_explainer.html new file mode 100644 index 0000000..4114648 --- /dev/null +++ b/assets/hw/hw_mvp_explainer.html @@ -0,0 +1,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>:真实的物理电路。输入电压加上后,电流按电阻自然流动,节点电压在微秒级"稳定下来"(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 里的"隐层激活值"。它不被任何人直接设定——是电流平衡自己找到的值。"> + <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>:连接两个节点的"电阻",导电能力由旁边绿色电容的电压控制——<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 的小电流,这里的电压介于"网络自己的回答"和"正确答案"之间——偏向正确答案一点点(推力强度 = β)。"> + <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>:和左边网络位置对应的边共享同一颗权重电容——所以两份网络的"权重"永远相同,差别只来自输出端的推力。"> + <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 网络"知道"它——通过 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>:持续测量"输出与目标的差距",按差距大小注入一股小电流把输出往目标推。推力系数就是 EP 里的 β。它相当于告诉网络:"往这边一点点"——但从不告诉任何一条边该怎么改。"> + <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 的导电性。这就是一次"梯度更新"——纯模拟、纯局部、连续发生。"> + <g id="cells"></g> + </g> + + <!-- learn/freeze switch --> + <g data-tip="<b>学习/冻结开关(机械拨杆)</b>:断开所有电容的充电电流 = 冻结权重(相当于 model.eval())。故意用机械开关而不是芯片——保住"学习回路里没有任何数字器件"的主张。"> + <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>:观察节点电压用,不在任何反馈或学习回路里。这是"完全模拟"主张允许的唯一仪器。"> + <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> |
