Upload web/docs/ARCHITECTURE.md with huggingface_hub
Browse files- web/docs/ARCHITECTURE.md +120 -0
web/docs/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture
|
| 2 |
+
|
| 3 |
+
How a page load becomes a training node, and how a handful of browsers stay
|
| 4 |
+
bit-identical while training one model together.
|
| 5 |
+
|
| 6 |
+
```
|
| 7 |
+
browser A βββ βββ browser B
|
| 8 |
+
WebGPU/CPU β WebRTC data channels β WebGPU/CPU
|
| 9 |
+
verified βββββββββ gradients ββββββ€ verified
|
| 10 |
+
INT8 units β β INT8 units
|
| 11 |
+
βββββ server.js (signaling + static + /data) βββββ
|
| 12 |
+
never sees weights or gradients
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
## The pieces
|
| 16 |
+
|
| 17 |
+
| File | Role |
|
| 18 |
+
|---|---|
|
| 19 |
+
| `server.js` | WebSocket **signaling** (introduces peers, relays WebRTC offers/ICE), static hosting, and the `/data` endpoint that streams FineWeb-Edu text. It never sees weights or gradients. |
|
| 20 |
+
| `public/app.js` | the WebRTC mesh, the training loop, gradient averaging, checkpoints, and the sync guard. |
|
| 21 |
+
| `public/transformer.js` | the mini transformer LM (attention + MLP blocks, next-token prediction) β forward and backward, every multiply through the units. |
|
| 22 |
+
| `public/verified_core.js` | the **verified INT8 units**: block-scaled quantize β exact LUT multiply β int32 accumulate β bit-exact f32 epilogue; plus the JS mirrors, the audit, and the MLP chain reference. |
|
| 23 |
+
| `public/webgpu.js` | the WGSL kernels (LUT matmul, DP4A int8 dot, fused attention, B2B MLP chain) and the **exact init gates** that admit them. |
|
| 24 |
+
| `public/traincore.js` | float reference trainer + deterministic Adam. |
|
| 25 |
+
| `public/*.bin` | the units as lookup tables (`mul_lut` is a 65536-entry exact int8 product table). |
|
| 26 |
+
|
| 27 |
+
## Peers, groups, and the leader
|
| 28 |
+
|
| 29 |
+
Peers are grouped by **public IP** (same network β same group) or by an
|
| 30 |
+
explicit `?room=CODE` (with host approval per join). Inside a group every
|
| 31 |
+
pair gets a WebRTC data channel β a full mesh; the signaling server is only
|
| 32 |
+
used for the handshake.
|
| 33 |
+
|
| 34 |
+
Whoever presses **Start** becomes the **leader** for that run: their settings
|
| 35 |
+
are broadcast to everyone, they sync mid-run joiners (weights + step), and
|
| 36 |
+
they are the source of truth for gradient repair (below). Everything else is
|
| 37 |
+
symmetric β every device computes, sends, and averages the same way.
|
| 38 |
+
|
| 39 |
+
## One training step
|
| 40 |
+
|
| 41 |
+
1. Each device samples its own batch from its data shard and computes the
|
| 42 |
+
loss and gradient **through the verified units**.
|
| 43 |
+
2. It broadcasts `[step | weight-hash | probe-hash | loss | gradient]` to
|
| 44 |
+
every peer and collects everyone else's.
|
| 45 |
+
3. When it has a gradient from **every device in the step roster**, it
|
| 46 |
+
averages them **in strict roster order** β float addition is not
|
| 47 |
+
associative, so a different order would round differently and fork the
|
| 48 |
+
weights β and applies one Adam update. Adam's state is identical
|
| 49 |
+
everywhere, so nothing but the gradients ever crosses the wire.
|
| 50 |
+
4. Because every device starts from the same seeded weights and applies the
|
| 51 |
+
same averaged update with the same rounding, the replicas are
|
| 52 |
+
**bit-identical** β verified continuously by the weight hash.
|
| 53 |
+
|
| 54 |
+
### The sync guard
|
| 55 |
+
|
| 56 |
+
Before applying an update, each device checks every peer's **weight hash**
|
| 57 |
+
(FNV-1a of the full weight bytes before the step) against its own. Any
|
| 58 |
+
mismatch means the replicas have forked β the guard **stops the run** rather
|
| 59 |
+
than train past a divergence. The **probe hash** is different: it re-runs a
|
| 60 |
+
fixed seeded int8 GEMM through the device's live kernel each step, so a
|
| 61 |
+
device whose *arithmetic* has gone wrong is caught even while its weights
|
| 62 |
+
still match (weights only depend on the gradient bytes everyone receives).
|
| 63 |
+
|
| 64 |
+
### Gradient repair (asymmetric meshes)
|
| 65 |
+
|
| 66 |
+
WebRTC meshes can be asymmetric: a gradient can reach the leader but not some
|
| 67 |
+
follower. Instead of stalling, a follower missing a roster gradient asks the
|
| 68 |
+
**leader** to re-send that peer's gradient for that step (the leader retains
|
| 69 |
+
the last 8 steps of everyone's gradients). The repair is bit-exact β the
|
| 70 |
+
follower averages the identical bytes β so the run continues without a fork.
|
| 71 |
+
Only the leader may vouch for another peer's gradient.
|
| 72 |
+
|
| 73 |
+
## The wire protocol
|
| 74 |
+
|
| 75 |
+
All messages are binary on the data channel. Gradient messages start with a
|
| 76 |
+
non-negative `int32 step`; control messages use negative sentinels:
|
| 77 |
+
|
| 78 |
+
| Sentinel | Message |
|
| 79 |
+
|---|---|
|
| 80 |
+
| `-2` | checkpoint push (one device restores the whole group) |
|
| 81 |
+
| `-3` | run config from the starter (`c, t, b, steps, lr`) |
|
| 82 |
+
| `-4` | step roster (who is in this step) |
|
| 83 |
+
| `-5` | fragment β large messages are chunked at 48 KB |
|
| 84 |
+
| `-6` | mid-run resume (weights + step for a late joiner) |
|
| 85 |
+
| `-7` | repair request (step, peer) |
|
| 86 |
+
| `-8` | repair response (leader-only, original header + bytes) |
|
| 87 |
+
|
| 88 |
+
Gradient wire format: `[i32 step][u32 whash][u32 phash][f32 loss][f32 gradβ¦]`.
|
| 89 |
+
|
| 90 |
+
## Compute backends
|
| 91 |
+
|
| 92 |
+
At init, `webgpu.js` tries the best available backend and **gates** each
|
| 93 |
+
kernel before use (see [VERIFICATION.md](VERIFICATION.md)):
|
| 94 |
+
|
| 95 |
+
1. **DP4A** (`packed_4x8_integer_dot_product`) β hardware int8 dot product.
|
| 96 |
+
2. **LUT shader** β the multiply table as a WebGPU storage buffer; also the
|
| 97 |
+
oracle twin every other kernel is compared against.
|
| 98 |
+
3. **CPU (JS)** β the same units in plain JavaScript. Not an approximation:
|
| 99 |
+
the CPU mirror and the GPU kernels produce the **same bits**, which is
|
| 100 |
+
what lets GPU and CPU devices train in one group.
|
| 101 |
+
|
| 102 |
+
There is no plain-float forward path β every multiply in the model goes
|
| 103 |
+
through the units on every backend.
|
| 104 |
+
|
| 105 |
+
## Training data
|
| 106 |
+
|
| 107 |
+
`server.js` exposes `/data`: it picks a random slice from the FineWeb-Edu
|
| 108 |
+
10BT parquet shards and reads it **directly off the HF CDN with HTTP range
|
| 109 |
+
requests** (pure-JS `hyparquet`, SNAPPY decompression, results cached). No
|
| 110 |
+
datasets-server dependency. Browsers fetch `/data` per batch; devices that
|
| 111 |
+
cannot reach it fall back to a small built-in corpus. The dataset is
|
| 112 |
+
hardcoded β every group trains on FineWeb-Edu.
|
| 113 |
+
|
| 114 |
+
## Checkpoints and the inference kit
|
| 115 |
+
|
| 116 |
+
The `.pt` checkpoint stores magic/version, config dims, step, and the flat
|
| 117 |
+
f32 weights; the loader validates tokenizer vocab and dimensions before
|
| 118 |
+
accepting, and a load **broadcasts** to the group (sentinel `-2`). The
|
| 119 |
+
**inference kit** is a generated single-file HTML with the model code and the
|
| 120 |
+
current weights (base64) baked in β offline generation anywhere.
|