docs: reverse-engineer the BitBrain algorithm from its C source; assess as a gun
Reads docs/BitBrain_C_code.zip end to end (full_mnist_2048.c 568 lines + bitarray.h), reconciles every weight/data file's byte size against its loader, and answers two questions: 1. What the algorithm is: signed thresholded address decoders (random projections) -> write-once sparse binary coincidence bit tensors -> counting/argmax readout. Thresholds and ADs come from an unsupervised program that is NOT in the zip; only the SBC tensors are learned here. The supervised rule is genuinely online, single-pass, order-free, and has no learning rate - so it satisfies ModularBot's reset-on-enemy-change constraint. Found and isolated a real bug: read_from_sbc's uint8_t bit_test truncates the 32-bit bit test, so only ADEs with i%%32 < 8 are ever counted. Shipped reader: 96.540%% (reproduced exactly); with the bug fixed: 97.210%%. 2. Whether it can become a gun: not on this evidence. Measured full inference at ~0.56 ms/sample (cost is not the blocker), but the AD layer is unobtainable, and this repo has already shown the binding constraint is the target signal, not the learner - the TM head scored 2.08pp below its own majority class and the AD/SBC primitives are already in the 16-family shootout as WiSARD/Bloom/SDM. Recommends a readout swap on the existing WiSARD feature extractor and a Gate-2b-style >=80%% side-accuracy gate before any port. Also: recommends committing the 11 MB zip under its explicit name.
This commit is contained in:
@@ -0,0 +1,646 @@
|
||||
# BitBrain — what the algorithm actually is, and whether it can become a gun
|
||||
|
||||
**Issue:** N/A (read-only research requested by orchestrator)
|
||||
**Date:** 2026-09-24
|
||||
**Primary source:** `docs/BitBrain_C_code.zip` (11,656,362 B, untracked), extracted to
|
||||
`/tmp/bitbrain/BitBrain_C_code/`. Everything below cites that tree:
|
||||
- `full_mnist_2048.c` — 568 lines, the whole implementation
|
||||
- `bitarray.h` — 41 lines, bit-array macros (`uint32_t` slots; `BITSET` 35, `BITCLEAR` 36, `BITTEST` 37, `BITNSLOTS` 39)
|
||||
- pretrained weights `AD1_2048 … AD4_2048`, `thresh1_2048 … thresh4_2048`
|
||||
- MNIST data `train_data … test_label` (the classic uint8 MNIST dump)
|
||||
|
||||
The C file carries a GNU GPL-3.0-or-later header, `© 2022 The University of Manchester`
|
||||
(`full_mnist_2048.c:1-19`). The zip contains **no build files, no header, no README, and
|
||||
no unsupervised-learning program**.
|
||||
|
||||
**Verification legend:**
|
||||
|
||||
| Tag | Meaning |
|
||||
|---|---|
|
||||
| `[MEASURED]` | I compiled and/or ran the code (or an exact verbatim copy of it) here and read the number off the output. |
|
||||
| `[VERIFIED]` | I read the exact line, and/or reconciled it against byte sizes / arithmetic I recomputed. |
|
||||
| `[INFERRED]` | My reasoning from code + measurements; not directly observed. |
|
||||
| `[UNKNOWN]` | I could not determine it from the primary source. |
|
||||
|
||||
Unless stated otherwise, every measurement was taken with `gcc 15.3.0`, `-O3`, **single
|
||||
threaded** (OpenMP is off by default — see §4.1), on this machine (16 cores).
|
||||
|
||||
---
|
||||
|
||||
## 0. One-paragraph answer
|
||||
|
||||
BitBrain (as shipped here) is a **three-stage, purely feed-forward pipeline**: (1) a
|
||||
**random-projection threshold layer** of 2048 "address decoders" (ADs) per layer, across 4
|
||||
layers with 6/8/10/12 synapses each, whose ~74000 integer `(position, sign)` weights and
|
||||
2048×4 integer firing thresholds arrive **as two files from a program we do not have**
|
||||
(`full_mnist_2048.c:376-384`, `409-417`); (2) a **sparse binary coincidence (SBC) memory** —
|
||||
six `2048×2048×10` bit tensors (31.5 MB) that are written once per labelled sample, a *pure
|
||||
`BITSET`, never cleared* (`full_mnist_2048.c:129-152`); (3) a **counting readout** that sums,
|
||||
over the 6 memories, how many of the observed AD-pair coincidences are "known" for each of
|
||||
the 10 labels and takes the argmax (`full_mnist_2048.c:157-181`, `527-540`). The program
|
||||
prints `346 wrong = 96.540 pct correct` on all 10,000 MNIST test images `[MEASURED]`, but it
|
||||
does so **with a real bug**: the readout's `uint8_t bit_test` truncates the 32-bit bit-test
|
||||
result, so only ADs with `i % 32 < 8` are ever counted (`full_mnist_2048.c:161,173-175`).
|
||||
Removing that truncation gives **97.210%** `[MEASURED]`. Stages 2 and 3 are genuinely
|
||||
**online, single-pass, per-sample, order-free** and therefore satisfy the `ModularBot`
|
||||
constraint; stage 1 is the part we cannot rebuild from this source. On the gun question the
|
||||
honest answer is: **no, not on this evidence.** The learner is cheap (0.56 ms/sample for one
|
||||
full forward pass here `[MEASURED]`), but this repo has already evaluated every structurally
|
||||
equivalent learner on this target — WiSARD/n-tuple LUTs (which *are* the AD primitive),
|
||||
Tsetlin, Kanerva SDM, Bloom filters, HDC/VSA — and the binding constraint was the **target
|
||||
signal**, not the learner (`BNNBot_garage/analysis/shootout_results.md`,
|
||||
`common_libs/tests/tm_pattern_sweep_results.md:611-631`, `common_libs/guns/tm_horizon.nim:49-53`).
|
||||
BitBrain would be the third learner swapped onto that same signal-poor target.
|
||||
|
||||
---
|
||||
|
||||
## 1. What the algorithm is
|
||||
|
||||
### 1.1 The core primitive: one ADE = one signed, thresholded random projection
|
||||
|
||||
`find_AD_firing_pattern` (`full_mnist_2048.c:98-124`) is the whole of stage 1. For each of
|
||||
the `w` ADEs in a layer, for each of the `n` synapses of that ADE:
|
||||
|
||||
```c
|
||||
if( address_decoder[i][j] > 0 ) { yang = 64; position = address_decoder[i][j]; } // line 112
|
||||
else { yang = -64; position = -address_decoder[i][j]; } // line 114
|
||||
pixel = sensory_input[ loop ][ position-1 ]; // line 116
|
||||
count += ( pixel - 127 ) * yang; // line 118
|
||||
...
|
||||
ad_row_fired[ i ] = ( count >= ad_row_thresh[ i ] ? TRUE : FALSE ); // line 122
|
||||
```
|
||||
|
||||
So, precisely `[VERIFIED]`:
|
||||
|
||||
- An **address decoder element (ADE)** is described by an `n`-long vector of non-zero
|
||||
`int32_t`. **The sign is the synapse sign** — positive = excitatory, negative = inhibitory
|
||||
(`full_mnist_2048.c:90-91`, doc comment). **The magnitude is the input position**, 1-based
|
||||
into the flattened input row (`position-1`, line 116).
|
||||
- `count` is a **signed, 127-centred, x64-scaled sum of the sampled input bytes**. `yang` is
|
||||
only ever `±64`, a pure scale factor; it is not a learned weight. So
|
||||
`count/64 = Σ_j ±(pixel_j − 127)`, i.e. each "pixel" is treated as a real in `[−127, 128]`
|
||||
with 127 as the decision centre. `[VERIFIED]`
|
||||
- The ADE fires iff `count >= ad_row_thresh[i]` — a **≥** comparison against a per-ADE
|
||||
integer threshold, i.e. the ADE is a thresholded linear unit on a random `n`-dimensional
|
||||
subspace. `[VERIFIED]`
|
||||
- The output is a dense `uint8_t[w]` 0/1 vector, `ad_row_fired` (line 122).
|
||||
|
||||
The doc comment calls the input "`0.255` for E/MNIST" (`full_mnist_2048.c:84`) — a typo for
|
||||
"0..255"; the values are raw MNIST `uint8` `[VERIFIED]`.
|
||||
|
||||
**Layer geometry** (`full_mnist_2048.c:345-352,358`): `W = 2048` ADEs per layer; four layers
|
||||
with `N1..N4 = 6, 8, 10, 12` synapses; `SBC_CT = 6` coincidence memories, one for each of the
|
||||
6 unordered layer pairs: (1,2) (1,3) (1,4) (2,3) (2,4) (3,4) — see the six `write_to_sbc`
|
||||
calls at `470-480` and the six `read_from_sbc` calls at `516-525`. `D = 10` label classes
|
||||
(line 347). Input width `INPUT_SZ = 784` (line 356).
|
||||
|
||||
**This is an n-tuple / RAM-node / WiSARD-style address decoder with a threshold** `[INFERRED]`
|
||||
— i.e. the classic "random receptive field + threshold" feature layer. It is not novel
|
||||
machinery; this repo already has one (`BNNBot_garage/src/wisard_predictor.nim`, K=14, 50
|
||||
LUT nodes, ~16384 entries each, averaged readout).
|
||||
|
||||
### 1.2 Sparsity (measured)
|
||||
|
||||
| Layer | n | mean ADEs fired / 2048 | mean fire rate |
|
||||
|---|---:|---:|---:|
|
||||
| 1 | 6 | 25.4 | 1.24% |
|
||||
| 2 | 8 | 26.7 | 1.30% |
|
||||
| 3 | 10 | 28.7 | 1.40% |
|
||||
| 4 | 12 | 29.8 | 1.45% |
|
||||
|
||||
`[MEASURED]` — 2,000 test images, verbatim `find_AD_firing_pattern`. Over 20,000 training
|
||||
images the rates are 1.269 / 1.355 / 1.461 / 1.504% and **every** image fires ≥1 ADE in every
|
||||
layer (so no image is ever "empty") `[MEASURED]`.
|
||||
|
||||
The per-ADE rate distribution is **very heterogeneous** `[MEASURED]` (20,000 train images):
|
||||
|
||||
| Layer | never fires | 1–2% | 2–3% | ≥10% | hottest single ADE |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 1 | 778 (38%) | 1187 | 67 | 7 | 51.90% |
|
||||
| 2 | 656 (32%) | 1272 | 97 | 9 | 23.82% |
|
||||
| 3 | 592 (29%) | 1238 | 163 | 13 | 31.37% |
|
||||
| 4 | 522 (25%) | 1293 | 175 | 17 | 12.75% |
|
||||
|
||||
This is *not* a uniform quantile calibration: a third of the ADEs are dead and a handful are
|
||||
heavy hitters. That is the fingerprint of a selection/pruning process, not a global rule.
|
||||
|
||||
### 1.3 What is trainable HERE vs PRETRAINED ELSEWHERE — **the decisive split**
|
||||
|
||||
`[VERIFIED]` — reading the file end to end, the only learnable things updated by code in this
|
||||
tree are the bits of the six SBC tensors.
|
||||
|
||||
| Component | Where it comes from | Learned in this file? |
|
||||
|---|---|---|
|
||||
| 4 × 2048 firing thresholds (`thresh*_2048`) | **file**, loaded `full_mnist_2048.c:414-417`; documented "thresholds from unsupervised learning" (`376-379`) | **No** |
|
||||
| 4 × 2048 ADs (`AD*_2048`) | **file**, loaded `full_mnist_2048.c:409-412`; documented "ADs from unsupervised learning" (`381-384`) | **No** |
|
||||
| 6 × `2048×2048×10` SBC bit tensors | `calloc`'d zero at `full_mnist_2048.c:386-391` | **Yes** — `write_to_sbc`, `129-152`, called in the loop at `435-482` |
|
||||
|
||||
There is **no** code anywhere in the 568 lines that modifies an AD or a threshold. I listed the
|
||||
complete function inventory: `find_AD_firing_pattern` (98), `write_to_sbc` (129),
|
||||
`read_from_sbc` (157), four file loaders (184, 209, 234, 259), three display helpers (284, 296,
|
||||
310), and `main` (329) `[VERIFIED]`. `show_thresholds`/`show_AD` are `printf`-only and are
|
||||
behind a commented-out `#define SHOW_LOADED_FILES` (`420-431`). `[VERIFIED]`
|
||||
|
||||
**Say it loudly: we cannot regenerate the AD layer from this zip.** The unsupervised program
|
||||
that produced `AD*_2048` and `thresh*_2048` is not present, not referenced, and not described
|
||||
beyond the two comments above. Any BitBrain gun must therefore **either** ship those exact
|
||||
files **or** synthesise a substitute AD layer of its own — and the original was trained on
|
||||
784-pixel MNIST, so the files are useless for any other input domain. There is no third
|
||||
option.
|
||||
|
||||
I did, however, characterise the files well enough to say what a substitute would have to look
|
||||
like, and how much of the original's structure is *recoverable* from the unlabelled input
|
||||
distribution alone.
|
||||
|
||||
#### 1.3.1 Structure of the AD files (this is recoverable — partly)
|
||||
|
||||
`[MEASURED]`, from the four `AD*_2048` files:
|
||||
|
||||
- **Magnitudes** (`|AD|`, which *is* the input position) lie in `[37, 776]` for every layer,
|
||||
with **quasi-linear deciles** (`L4`: min 37, then 206/261/301/355/409/462/514/556/610, max
|
||||
776). So the magnitudes look like near-uniform random draws over the usable index range.
|
||||
- The **range itself is explained by dead pixels**: pixels `1..36` (top row + part of the
|
||||
second) and `777..784` (bottom-right corner) are inked in **0 of 3,000** training images,
|
||||
and those exact positions are sampled **0 times** by every layer `[MEASURED]`. That is why
|
||||
the minimum `|AD|` is 37 and the maximum is 776 rather than 1 and 784.
|
||||
- Of the 134 pixels that are constant across 3,000 images, 133 / 134 / 127 / 132 are
|
||||
**never** used by layers 1..4 respectively — i.e. the position pool excludes constant
|
||||
pixels.
|
||||
- Position usage tracks input activity almost perfectly: **Spearman(ink frequency,
|
||||
AD-slot usage) = 0.994** over the 784 pixels; 75.9% of all AD synapse slots sit on the 219
|
||||
pixels that are inked in more than a third of images, and the 134 dead pixels get exactly
|
||||
**0.0%** `[MEASURED]`.
|
||||
- Synapse signs are **not** 50/50: excitatory fractions are 44.7% / 42.9% / 40.3% / 39.2%
|
||||
|
||||
`[MEASURED]`.
|
||||
- Still unexplained: ~93–114 *inked* pixels are never sampled by each layer, and the 220–247
|
||||
unused positions per layer have a median ink-count of **0** with max 33–89 (vs a median of
|
||||
619–698 for used positions) `[MEASURED]`. There is a real selection rule beyond "drop dead
|
||||
pixels" that I could not reverse-engineer `[UNKNOWN]`.
|
||||
- **Thresholds are not per-ADE fire-rate quantiles.** For each layer, 13–25 ADEs have a
|
||||
threshold **above the maximum `count` that ADE can ever produce**, so they can never fire
|
||||
(1.22% / 0.68% / 0.98% / 0.63% of ADEs) `[MEASURED]`. A quantile calibration on the same
|
||||
data can never produce that. A handful have negative thresholds (`thresh1` min −10871,
|
||||
`thresh4` min −15496) but **no** ADE always fires `[MEASURED]`.
|
||||
|
||||
**Consequence for a port:** a substitute AD layer could be synthesised cheaply from *unlabelled*
|
||||
samples of the new input domain — (a) drop constant positions, (b) sample positions with
|
||||
probability monotone in activity, (c) uniform random `±` signs, (d) set each threshold to a
|
||||
quantile of that ADE's own `count` distribution to hit a target fire rate (~1.3–1.5%) — but it
|
||||
would be a **statistically similar, not the same, feature layer**, and the only way to validate
|
||||
it is end-to-end accuracy on the target task. There is no apples-to-apples baseline to compare
|
||||
against because the original ADs are MNIST-only. That is the single largest technical risk in
|
||||
the whole idea `[INFERRED]`.
|
||||
|
||||
### 1.4 Binarisation and the `count`↔bit-width relationship
|
||||
|
||||
There is **no binarisation** before the ADs: `sensory_input` is the raw `uint8` MNIST matrix,
|
||||
784 bytes per row (`full_mnist_2048.c:234-256` loader, `346-356` sizes) `[VERIFIED]`. The ADE
|
||||
does the analogue-to-threshold conversion itself, via `(pixel − 127) * yang` (line 118).
|
||||
|
||||
The bit-width note: `count` is `int32_t` (line 101), and its per-synapse term is
|
||||
`int8_t(±64) × (pixel − 127)`, so one synapse contributes at most `64 × 128 = 8192`. With
|
||||
`n = 12` the maximum `|count|` is `98304`, which **overflows `int16_t`** — hence the `int32_t`
|
||||
for both `count` and `ad_row_thresh`. `pixel` is `int16_t` (line 103), which is sufficient for
|
||||
0..255. `[VERIFIED]` And indeed the largest threshold in the data, 99053 (`thresh4_2048`), sits
|
||||
just above that 98304 ceiling `[MEASURED]`.
|
||||
|
||||
### 1.5 The supervised head: `write_to_sbc` / `read_from_sbc`
|
||||
|
||||
**Write (learning)** — `full_mnist_2048.c:129-152`, comment at `128`:
|
||||
|
||||
```c
|
||||
FOR_LOOP(i, w) if (first[i])
|
||||
FOR_LOOP(j, w) if (second[j]) {
|
||||
tens_index = D3(i, j, label); // 138: (i, j, class) -> one bit
|
||||
if (!BITTEST(mem, tens_index)) { internal_count++; BITSET(mem, tens_index); } // 142-144
|
||||
}
|
||||
```
|
||||
|
||||
`D3(i,j,k) = i + j*wd + k*ws` with `wd = W*D` and `ws = W` (`full_mnist_2048.c:68`, globals set
|
||||
at `360`), so the bit layout is `[j][label][i]` with `i` fastest. `[VERIFIED]`
|
||||
|
||||
Thus the learnable rule is exactly: **set the bit at (coincidence of an ADE from layer A firing
|
||||
and an ADE from layer B firing, current label)**. Nothing is ever decremented or cleared; there
|
||||
is **no learning rate, no counter, no decay, no negative evidence** `[VERIFIED]`. The six
|
||||
`write_to_sbc` calls per sample (train loop `435-482`, calls at `470-480`) each receive the
|
||||
label as `uint8_t` (`464`).
|
||||
|
||||
**Read (inference)** — `full_mnist_2048.c:157-181`:
|
||||
|
||||
```c
|
||||
FOR_LOOP(i, w) if (first[i])
|
||||
FOR_LOOP(j, w) if (second[j])
|
||||
FOR_LOOP(k, ds) { tens_index = D3(i, j, k); // 171
|
||||
bit_test = BITTEST(mem, tens_index); // 173 <-- uint8_t!
|
||||
if (bit_test) (store[k])++; } // 175
|
||||
```
|
||||
|
||||
then the six per-memory count vectors are summed and argmaxed (`527-540`). So the score for
|
||||
class `k` is *the number of observed AD-pair coincidences whose bit for `k` is set* — an
|
||||
**overlap count**, not a likelihood and not a probability. `[VERIFIED]`
|
||||
|
||||
Two consequences worth stating plainly:
|
||||
|
||||
1. **No normalisation.** There is no division by the number of stored features per class, so
|
||||
the score is biased toward whichever class has *more bits stored* — a class seen more
|
||||
often, or whose features are more diverse, wins ties for free. On balanced MNIST this is
|
||||
mild; on a gun with a few hundred labels it is a real hazard. `[INFERRED]` from lines
|
||||
175 + 527-531.
|
||||
2. **No negative evidence.** A coincidence that is *never* seen for class `k` contributes 0,
|
||||
identical to a coincidence seen for *every* class. Frequent coincidences saturate to
|
||||
10/10 and contribute a constant to all classes (harmless for argmax); rare coincidences
|
||||
carry the entire discrimination. `[INFERRED]`
|
||||
|
||||
### 1.6 The `read_from_sbc` bug — found, isolated, and reproduced `[MEASURED]`
|
||||
|
||||
`bit_test` is declared `uint8_t` (line 161) but `BITTEST` yields the full masked 32-bit word
|
||||
(`bitarray.h:37`). Whenever the hit bit sits at word-position ≥ 8, `bit_test` truncates to 0
|
||||
and the hit is **silently dropped**. Since `D3` strides by `ws = W = 2048` (a multiple of 32)
|
||||
and `wd = W*D = 20480` (also a multiple of 32), the bit's position inside its `uint32` word is
|
||||
exactly `i % 32`. So **the shipped reader only counts ADEs with `i % 32 < 8` — three quarters
|
||||
of the AD range is invisible at inference time.** `[VERIFIED]` by arithmetic and `[MEASURED]`
|
||||
by reproduction.
|
||||
|
||||
Verification chain (all on the same trained memory, sample 0, layer pair (1,2)):
|
||||
|
||||
| Counter | Value |
|
||||
|---|---:|
|
||||
| Python, independent byte-level bit test, full `i` range | **319** |
|
||||
| Python, same but restricted to `i % 32 < 8` | **50** |
|
||||
| C, verbatim `read_from_sbc` | **50** |
|
||||
|
||||
The write path is unaffected: `write_to_sbc` uses `!BITTEST(...)` in a boolean context (line
|
||||
142), so no truncation occurs there. `[VERIFIED]` The bug is read-only and one-sided: 25% of
|
||||
the evidence is used.
|
||||
|
||||
End-to-end, on all 10,000 test images after the full 60,000-sample training pass `[MEASURED]`:
|
||||
|
||||
| Reader | Accuracy | ms/sample |
|
||||
|---|---:|---:|
|
||||
| `read_from_sbc` **as shipped** (with the truncation bug) | **96.540%** | 0.412 |
|
||||
| `read_from_sbc` with the truncation removed | **97.210%** | 0.531 |
|
||||
| sparse active-list reader, no truncation | **97.210%** | **0.202** |
|
||||
|
||||
The 96.540% row **exactly reproduces the number the unmodified program prints** `[MEASURED]`
|
||||
(`./bb` → `346 wrong = 96.540 pct correct`, 66 s wall, single-threaded). So the bug is real,
|
||||
it costs 0.67 pp, and it does not invalidate the paper's claim — but anyone porting this
|
||||
should fix it, and anyone benchmarking against "96.5%" should know it is a lower bound.
|
||||
|
||||
### 1.7 Is the supervised rule online / usable incrementally?
|
||||
|
||||
**Yes, unambiguously** `[VERIFIED]` from `129-152`: the update is per-sample, order-independent
|
||||
(it is an idempotent `BITSET`), single-pass, and needs no batch, no epoch, no learning rate,
|
||||
and no revisit of older samples. Inference is available at any point, including before any
|
||||
training. The only thing that is *not* cheap is resetting: the memory is monotone, so
|
||||
"forget the old enemy" means clearing 31.5 MB (`calloc`/`memset`) and starting over.
|
||||
|
||||
The learning curve is monotone and does **not** collapse `[MEASURED]` (cumulative single pass
|
||||
over the training set, evaluated on the first 2,000 test images):
|
||||
|
||||
| training samples | per class | acc, shipped (buggy) reader | acc, correct reader |
|
||||
|---:|---:|---:|---:|
|
||||
| 600 | 60 | 79.05% | 82.50% |
|
||||
| 1,200 | 120 | 85.05% | 86.85% |
|
||||
| 2,400 | 240 | 87.80% | 89.75% |
|
||||
| 4,800 | 480 | 90.80% | 92.20% |
|
||||
| 12,000 | 1,200 | 93.05% | 93.90% |
|
||||
| 30,000 | 3,000 | 94.20% | 95.30% |
|
||||
| 60,000 | 6,000 | 95.20% | 95.70% |
|
||||
|
||||
So it needs **~a few hundred labelled samples per class** before it is useful and ~2,000+ to
|
||||
approach its ceiling. There is no saturation collapse: at 60,000 samples only 18.9–20.8% of
|
||||
each SBC tensor's bits are set, 70.8–76.3% of `(i,j)` pairs are non-empty, and only
|
||||
**0.03–0.05%** of non-empty pairs have all 10 labels set `[MEASURED]`. I initially expected
|
||||
monotone saturation to destroy discrimination; it does not, on this data volume.
|
||||
|
||||
### 1.8 Exact dimensions and on-disk format — reconciled against byte sizes
|
||||
|
||||
All loaders read **one raw contiguous blob, no header, no magic, no dimension fields,
|
||||
native-endian `int32_t`/`uint8_t`** `[VERIFIED]`. Nothing in a file records or checks the
|
||||
geometry; `rows`/`cols` come from the caller's `#define`s in `main` (`345-358`).
|
||||
|
||||
- `load_int32_mat_from_file(file, into, rows, cols)` (`184-207`) does
|
||||
`fread(&into[0][0], sizeof(int32_t), rows*cols, infile_ptr)` — a single blob into the
|
||||
row-major backing store created by `HEAP_MAT` (`52`, `name[i] = name[i-1] + szc`). So the
|
||||
file is **row-major `int32[rows][cols]`**.
|
||||
- `load_int32_vec_from_file` (`209-232`) — `int32[size]`.
|
||||
- `load_uint8_mat_from_file` (`234-257`) — row-major `uint8[rows][cols]`.
|
||||
- `load_uint8_vec_from_file` (`259-282`) — `uint8[size]`.
|
||||
- All four `printf` a warning and continue if the read is short (`197-198` etc.); only a
|
||||
missing file returns `FALSE`, and **no caller checks the return value** (`409-417`)
|
||||
`[VERIFIED]`.
|
||||
|
||||
Reconciling declared dims against `ls -l` `[MEASURED]`:
|
||||
|
||||
| File | bytes | declaration | arithmetic | OK |
|
||||
|---|---:|---|---|:--:|
|
||||
| `AD1_2048` | 49,152 | `load_int32_mat_from_file("AD1_2048", AD1, W, N1)`, `409`, `AD1 = W×N1` `381` | 2048 × 6 × 4 | ✓ |
|
||||
| `AD2_2048` | 65,536 | `410`, `382` | 2048 × 8 × 4 | ✓ |
|
||||
| `AD3_2048` | 81,920 | `411`, `383` | 2048 × 10 × 4 | ✓ |
|
||||
| `AD4_2048` | 98,304 | `412`, `384` | 2048 × 12 × 4 | ✓ |
|
||||
| `thresh1..4_2048` | 8,192 each | `414-417` | 2048 × 4 | ✓ |
|
||||
| `train_data` | 47,040,000 | `234`, `TRAIN_SZ × INPUT_SZ` `354,356` | 60000 × 784 × 1 | ✓ |
|
||||
| `train_label` | 60,000 | `259` | 60000 × 1 | ✓ |
|
||||
| `test_data` | 7,840,000 | `234`, `355` | 10000 × 784 × 1 | ✓ |
|
||||
| `test_label` | 10,000 | `259` | 10000 × 1 | ✓ |
|
||||
|
||||
Note the task brief's hint "49152 B = 2048 × 24 B" is right in bytes and equals `2048 × 6 ×
|
||||
int32`; the file is **not** packed — it is one `int32` per synapse. Labels are single bytes,
|
||||
not one-hot.
|
||||
|
||||
Derived memory (`full_mnist_2048.c:386-391`, `360`): each SBC tensor is
|
||||
`BITNSLOTS(2048·2048·10) = 1,310,720 uint32 = 5,242,880 B`; six of them = **31.46 MB**,
|
||||
zero-initialised by `calloc`. Plus 295 KB of ADs+thresholds, plus 47 MB of MNIST train data
|
||||
resident in RAM (`370-373`). Peak RSS is dominated by the data-file choice, not the model.
|
||||
|
||||
### 1.9 Hyperparameters that matter
|
||||
|
||||
| Knob | Value | Cite |
|
||||
|---|---|---|
|
||||
| ADEs per layer (`W`) | 2048 | `346` |
|
||||
| Layers | 4 | `349-352` |
|
||||
| Synapses per layer | 6 / 8 / 10 / 12 | `349-352` |
|
||||
| Classes (`D`) | 10 | `347` |
|
||||
| Input width | 784 | `356` |
|
||||
| SBC memories (`SBC_CT`) | 6, one per layer pair | `358`, `470-480` |
|
||||
| Label classes per memory | 10 (`D`) | `347` |
|
||||
| Synapse scale `yang` | ±64 | `112,114` |
|
||||
| Pixel centre | 127 | `118` |
|
||||
| Learning rate | **none** (idempotent bit-set) | `142-144` |
|
||||
| Firing threshold | per-ADE `int32`, from file | `122`, `414-417` |
|
||||
| Fire comparison | `count >= thresh` | `122` |
|
||||
| Epochs | 1 (single pass) | `436-482` |
|
||||
| Test protocol | all 10,000 images, argmax of summed counts | `488-548` |
|
||||
|
||||
### 1.10 The reported accuracy and how it is measured
|
||||
|
||||
`full_mnist_2048.c:550-553`:
|
||||
|
||||
```c
|
||||
printf("\n %u wrong = %6.3f pct correct \n", wrong, 100.0 - wrong / ( TEST_SZ / 100.0 ) );
|
||||
```
|
||||
|
||||
`wrong` accumulates only when `inf_label != label` (`543-548`), and a 10×10 confusion matrix is
|
||||
printed afterwards (`555-565`). `[VERIFIED]` This is honest top-1 accuracy on the full 10,000-
|
||||
image MNIST test set after a single pass over the full 60,000-image training set — but with
|
||||
the §1.6 truncation bug active. `[MEASURED] ./bb` prints `346 wrong = 96.540 pct correct` in
|
||||
66 s wall (single-threaded). The confusion matrix shows the usual MNIST structure: 9↔4, 9↔3,
|
||||
and 8↔3 are the biggest confusions; 1s and 0s are nearly perfect `[MEASURED]`.
|
||||
|
||||
**Nothing here is MNIST-independent.** `[VERIFIED]` The AD files' positions index 784 pixels;
|
||||
the thresholds are calibrated to the MNIST `count` distribution; `train_data`/`test_data` are
|
||||
hardcoded filenames inside `main` (`402-406`). This program is a demo, not a library.
|
||||
|
||||
---
|
||||
|
||||
## 2. Can this become a gun?
|
||||
|
||||
### 2.1 Can the C be linked as a library?
|
||||
|
||||
**Not in the shape it is in.** `[VERIFIED]` — here is the concrete evidence, item by item:
|
||||
|
||||
| Item | Finding | Cite |
|
||||
|---|---|---|
|
||||
| Clean callable API? | **Partially.** `find_AD_firing_pattern`, `write_to_sbc`, `read_from_sbc` are non-`static` with plain C signatures, so they *can* be called. But there is **no header**; the only header present is `bitarray.h`. | `98`, `129`, `157`; zip listing |
|
||||
| `main` does all the work? | **Yes.** Allocation (`363-400`), file loading (`402-417`), training (`435-482`), inference (`486-548`), reporting (`550-565`) are all inside `main` (`329`). | `329-567` |
|
||||
| Hardwired dimensions? | **In `main` only.** `W`, `D`, `N1..N4`, `TRAIN_SZ`, `TEST_SZ`, `INPUT_SZ`, `SBC_CT` are `#define`s in `main`'s body (`345-358`). The three core functions take `w` and `n` as parameters and are dimension-generic — **except** that they read the **globals** `ws`, `wd`, `ds` through the `D3` macro. Any caller must assign those first. | `68`, `360`, `62` |
|
||||
| File I/O inside the core? | **No.** All I/O is in `main` and the four loaders. The core is pure computation. | `184-282` |
|
||||
| OpenMP? | **Off by default** — `// #define USE_OPENMP` is commented out (`37`), with `#ifdef USE_OPENMP` at `38`. The `#pragma omp ...` directives at `440`, `466`, `501`, `513` are therefore inert and there is **no libgomp dependency**. Turning it on would also introduce shared mutable state across the parallel sections. | `37-38`, `440-480`, `513-525` |
|
||||
| Statics/globals? | Five **non-static globals** `ws, ds, wd, ww, wwd` (`62`) — linkable but externally writable, and `ws`/`wd`/`ds` are load-bearing. `class_count`/`confusion` are locals in `main`. | `62`, `68` |
|
||||
| Call-signature ergonomics | **Poor for per-tick use.** `find_AD_firing_pattern(loop, w, n, uint8_t** sensory_input, ...)` requires the **whole input matrix** to be resident and indexes it by `loop` (`116`). A single observation means passing a 1-row matrix with `loop = 0`. | `98`, `116` |
|
||||
| Licence | **GPL-3.0-or-later, © University of Manchester** (`1-19`). This repo has **no `LICENSE` file** and no licence statement in `README.md`/`CONTEXT.md` `[VERIFIED]`. Linking this C into the bot makes the bot GPL; that is an unmade decision, not a detail. | `1-19` |
|
||||
|
||||
**Concrete obstacle list per interop route:**
|
||||
|
||||
1. **`{.compile: "full_mnist_2048.c".}` + `{.importc.}`** — the fastest to try, but:
|
||||
- Nim's own generated C defines `main`, so the C file's `main` (`329`) is a **duplicate
|
||||
symbol at link time**. `{.compile.}` gives no way to inject `-Dmain=...` for that one
|
||||
file; you need a one-line shim, `bitbrain_shim.c`, that does
|
||||
`#define main bitbrain_unused_main` then `#include "full_mnist_2048.c"`, and compile *that*.
|
||||
- You must also `importc` the five globals (`62`) and assign `ws`/`wd`/`ds` (`360`) before
|
||||
the first call, or `D3` computes into unreachable memory. Silent wrong answers, not a crash.
|
||||
- `-O3 -march=native` are the file's own recommended flags (`full_mnist_2048.c:22-23`); via
|
||||
`{.compile.}` the whole Nim build's flags apply, not per-file ones. Workable, but it means
|
||||
the tuning in the header comment is lost.
|
||||
- `bitarray.h` must ship next to it (`35`).
|
||||
2. **Static archive** (`gcc -c` the shim → `ar rcs libbitbrain.a` → `{.passL: "-lbitbrain".}`)
|
||||
— cleanest link, same shim requirement, plus build-system plumbing and an
|
||||
architecture-specific binary artifact. The repo's `.gitignore` convention is **explicit
|
||||
binary names** (see §3), so a committed `.a` would be a new tracked binary that only works
|
||||
on one platform.
|
||||
3. **`{.header.}` / `importc` shim only** — impossible as-is: there is **no header** to point at.
|
||||
You would have to write `bitbrain_wrapper.h` + `bitbrain_wrapper.c` anyway. Once you are
|
||||
writing a wrapper, you may as well have the wrapper do the useful work: set the globals,
|
||||
expose `bb_set_dims(w, d)`, `bb_forward(const uint8_t* row, uint8_t** fired)`,
|
||||
`bb_train(const uint8_t* fired, uint8_t label)`, `bb_read(...)`, `bb_reset()`, and **fix
|
||||
the §1.6 truncation bug in the wrapper's own read loop**.
|
||||
|
||||
**Bottom line:** the C is 40 lines of trivial integer arithmetic plus two file loaders. What it
|
||||
is *not* is a library: it is a `main` with a hidden global dimension state and a broken reader.
|
||||
Either way you must write a wrapper. Given that, a **Nim port of §1.1/§1.5 is cheaper and
|
||||
safer than any interop route** — `[INFERRED]`, but note that a port is a derivative work, so it
|
||||
inherits the GPL question exactly; the licence is not dodged by porting.
|
||||
|
||||
### 2.2 The wall this project has already hit — and why BitBrain does not get around it
|
||||
|
||||
The brief's premise is right and the repo's own numbers back it:
|
||||
|
||||
- **The TM head scored below its own majority class.** `common_libs/tests/tm_pattern_sweep_results.md:626-631`:
|
||||
majority class 2 at **38.77%**, head accuracy **36.69%** (642473/1751067), **−2.08 pp**. The
|
||||
shuffled-label control sits at chance (20.04% vs 20.12%).
|
||||
- **Live side accuracy sat at chance.** The same file records online radial-head accuracy at
|
||||
**48.8% vs 19.9% shuffled** (`:273`) and the gated side head at **40.37%** vs a 38.75%
|
||||
majority (`:649`) — i.e. +1.6 pp for a learner with a gate.
|
||||
- **Every TM arm tied or lost to the shipped Pattern gun.** `docs/gun_rack_summary.md`: rack
|
||||
overall **6.95%** real hit rate, Tsetlin **5.8%**; disabling Tsetlin gave 6.18% → 5.76%
|
||||
(p = 0.57) `[VERIFIED]`.
|
||||
- **The break-even bar is already quantified.** `common_libs/guns/tm_horizon.nim:49-53`:
|
||||
gate 2b measured that a correcting head "needs ~80% side accuracy to break even on hits
|
||||
while the achievable signal is ~60%".
|
||||
- And this repo already ran a **16-family / ~260-config shootout** on the *same* aim target:
|
||||
`BNNBot_garage/analysis/shootout_results.md` — WiSARD (best generalist, 16.20 px MAE),
|
||||
Tsetlin (20.69 px, loses on all 4 enemies to cold start), Kanerva SDM (10.16 px, "no
|
||||
improvement on baseline"), Bloom filter (17.08 px, "worse than baseline"), HDC/VSA (broken),
|
||||
against a linear+Hebbian baseline at 17.61 px.
|
||||
|
||||
That last point is the one to sit with. **BitBrain's two stages are already represented in
|
||||
that shootout:**
|
||||
|
||||
| BitBrain stage | Already evaluated as | Result there |
|
||||
|---|---|---|
|
||||
| AD layer (random address decoders + threshold) | **WiSARD / n-tuple LUT nodes** (`BNNBot_garage/src/wisard_predictor.nim`, K=14, 50 nodes) | Best of 16 families, 16.20 px — and the repo still ships Pattern |
|
||||
| SBC coincidence memory + counting readout | **Bloom filter** (bit array, write-once) and **Kanerva SDM** (address→content memory) | 17.08 px and 10.16 px, neither beat the baseline on the metric that mattered |
|
||||
| (nearest TM analogue) | Tsetlin gun, TMHorizon | 5.8% real vs 6.95% rack; TMHorizon predicted to lose |
|
||||
|
||||
`[INFERRED]` So BitBrain is not a new kind of learner on this problem — it is a *sharper
|
||||
addressing scheme* on top of the same primitive the repo already tried, and its readout
|
||||
(binary "have I seen this coincidence with this label") is strictly weaker than WiSARD's
|
||||
running-mean readout, because it throws away the co-occurrence counts. Its one genuine
|
||||
advantage over the Tsetlin machinery is that it is **non-stochastic and order-free** (§1.7) —
|
||||
no learning rate to tune, no cold-start pathology, no clause-length collapse.
|
||||
|
||||
**What target signal would make BitBrain worth building?** Stated as a gate, mirroring the
|
||||
repo's own Gate-2b language: a target whose *offline* learnability, measured on held-out
|
||||
resolved shots, clears **≥80% side accuracy (or equivalent break-even on the hit metric)** and
|
||||
clears a **shuffled-label control at chance**. The published evidence is that the available
|
||||
side signal is ~60% `[VERIFIED]` (`tm_horizon.nim:49-53`). No learner change moves that
|
||||
number, and BitBrain is a learner change. **Porting BitBrain before finding such a target
|
||||
would be the third repetition of the same mistake** — a learner swapped onto a signal-poor
|
||||
target — and it would cost the AD-layer problem of §1.3 on top.
|
||||
|
||||
### 2.3 The runtime budget — measured, not extrapolated
|
||||
|
||||
Per the repo's own cost harness, the live budget is **~13.16 ms/tick (76 ticks/s)**
|
||||
(`common_libs/tests/measure_tm_pattern_cost.nim:6-9`); the TM Pattern gun measures
|
||||
**0.36 ms/tick** and Tsetlin ~**5.3 ms/tick** (`common_libs/guns/tm_horizon.nim:55-57`).
|
||||
**The brief's "1 ms/tick, TMHorizon 0.19 ms, Tsetlin 1.94 ms" numbers do not match what the
|
||||
repo records** — I am reporting the repo's numbers and flagging the discrepancy rather than
|
||||
guessing which is current. Either way the conclusion below does not change.
|
||||
|
||||
I compiled and ran the C. **MEASURED** (gcc 15.3.0 `-O3`, single-threaded, MNIST geometry
|
||||
784 → 2048 ADEs × 4 layers → 6 × 5 MB SBC tensors):
|
||||
|
||||
| Operation | Measured cost |
|
||||
|---|---:|
|
||||
| Stage 1: 4-layer `find_AD_firing_pattern` forward pass | **0.3599 ms/sample** |
|
||||
| Stage 3: shipped `read_from_sbc` (buggy, 6 memories) | **0.412 ms/sample** |
|
||||
| Stage 3: truncation removed (6 memories) | 0.531 ms/sample |
|
||||
| Stage 3: **sparse active-list read**, no truncation | **0.202 ms/sample** |
|
||||
| **Full inference = stage 1 + sparse stage 3** | **≈ 0.56 ms/sample** |
|
||||
| Stage 2: `write_to_sbc` training (dense scan, 6 memories) | 0.600 ms/sample |
|
||||
| Integration harness overhead, all 6 readers | 0.736 → 5.330 ms/sample as memories fill |
|
||||
| Full original program: 60,000 train + 10,000 test | **66 s wall**, `[MEASURED] ./bb` |
|
||||
|
||||
Two things to read off this:
|
||||
|
||||
- **Stage 1 is 0.36 ms; the shipped stage-3 reader is *more expensive* than the whole feature
|
||||
layer (0.41 ms), because `read_from_sbc` scans all 2048×2048 `j` slots per firing `i`
|
||||
irrespective of sparsity.** The sparse reader — iterate the ~25-element active list per
|
||||
layer instead of a 2048-slot scan — is **2.6× faster than the fixed reader and 2.0× faster
|
||||
than the shipped one**, and produces *bit-identical accuracy* (97.210% both) `[MEASURED]`.
|
||||
This is a free 2× win for any port.
|
||||
- **0.56 ms/sample is affordable.** It is in the same class as the TM Pattern gun's
|
||||
0.36 ms/tick, and it is a hard, non-iterative bound — no epochs, no retraining pass. Note
|
||||
however that the 0.202 ms sparse read is dominated by *cache misses* (~5,400 scattered
|
||||
probes into six 5 MB arrays per sample), not by arithmetic; a shooting-configuration SBC
|
||||
memory would be far smaller and proportionally faster. `[INFERRED]`
|
||||
- **Do NOT copy the tensor size.** 2048×2048×10 bits × 6 is 31.5 MB of L2/L3-hostile memory.
|
||||
A gun-sized version (say 512 ADEs × 2 layers × 6 classes) is ~0.8 MB and would not have this
|
||||
behaviour. `[INFERRED]`
|
||||
|
||||
### 2.4 The online-learning constraint — satisfied
|
||||
|
||||
`ModularBot` must learn inside one battle and must **not** persist across battles (state is
|
||||
wiped when the target changes — see the `resetLearning` contract documented at
|
||||
`common_libs/guns/tm_horizon.nim:37-40`, which is the repo's statement of that rule).
|
||||
|
||||
BitBrain's trainable part, judged against that `[VERIFIED]` from `129-152`:
|
||||
|
||||
| Requirement | BitBrain | Verdict |
|
||||
|---|---|---|
|
||||
| Per-sample update, no batch | `write_to_sbc` is called per sample | ✅ |
|
||||
| Order-independent | idempotent `BITSET` | ✅ |
|
||||
| No learning rate / hyperparameter to tune | none exists | ✅ |
|
||||
| Usable at inference before/while training | `read_from_sbc` reads whatever is set | ✅ |
|
||||
| Survives across rounds of the same battle | monotone accumulation | ✅ |
|
||||
| Wipes cleanly on enemy change | clear 31.5 MB (`memset`/re-`calloc`) | ✅ (cost, not correctness) |
|
||||
| Does not require old samples to be replayed | yes | ✅ |
|
||||
| Needs pretrained features from outside the battle | **yes — the ADs** | ⚠️ **This is the failure** |
|
||||
|
||||
So the *learner* satisfies the constraint perfectly; the *feature extractor* is a
|
||||
pre-battle artifact, which is exactly the thing `resetLearning` cannot regenerate and exactly
|
||||
the thing we do not have the program for.
|
||||
|
||||
### 2.5 Recommendation
|
||||
|
||||
**Do not port BitBrain as a gun now.** Reasoning, in priority order:
|
||||
|
||||
1. **The AD layer is unobtainable** (§1.3). This is not a nice-to-have; without it BitBrain is
|
||||
a counting head on top of a feature layer we design ourselves — at which point BitBrain's
|
||||
contribution is the counting head alone, and §2.2 already tells us what happens to
|
||||
learner-only changes on this target.
|
||||
2. **The signal is the wall, and it is already quantified** (§2.2): −2.08 pp vs majority for
|
||||
the TM head, live side accuracy at 47–49% (chance), break-even needing ~80% where the
|
||||
achievable signal is ~60%. BitBrain does not touch that.
|
||||
3. **The primitive is already in the repo's evaluated set** (§2.2): WiSARD *is* the AD layer,
|
||||
Bloom/SDM are the SBC memory, and none of them beat the incumbent on the metric that
|
||||
mattered.
|
||||
4. Cost is **not** the blocker (§2.3): ≈0.56 ms/sample is affordable. So the decision rests
|
||||
entirely on 1–3, and it comes out negative.
|
||||
|
||||
**What would change the verdict** — the cheap, honest next step, if the user wants one:
|
||||
|
||||
- **Reuse the AD primitive, not the learner.** `BNNBot_garage/src/wisard_predictor.nim` already
|
||||
implements an n-tuple address-decoder layer with a running-mean readout and eligibility
|
||||
traces, and it already won a 16-family shootout. If BitBrain's *counting* readout is the
|
||||
interesting part, the experiment worth running is a **readout swap on that existing
|
||||
feature extractor**, measured against the same fixture the shootout used — an offline,
|
||||
no-battle-change experiment, not a port.
|
||||
- **Gate it exactly like Gate 2b.** Do not build a gun until an offline corpus of resolved
|
||||
shots shows a target signal that clears the ≥80% side-accuracy break-even *and* a
|
||||
shuffled-label control at chance. If it does not, the answer is "no" regardless of learner.
|
||||
- If a port is ever justified: fix the §1.6 truncation bug, use the sparse active-list reader
|
||||
(§2.3, 2.6× faster for free), size the SBC tensors to the gun's feature count (not 2048²),
|
||||
normalise the per-class score by stored-feature count to kill the class-frequency bias
|
||||
(§1.5), and settle the GPL-3.0 question **before** writing the wrapper.
|
||||
|
||||
**How this could still fail even if we do everything right:** (a) a synthesised AD layer is
|
||||
statistically similar but not the same, and there is no MNIST-comparable baseline to validate
|
||||
it against (§1.3.1); (b) the class-frequency bias with a few hundred labels per class will be
|
||||
much worse than on balanced MNIST (§1.5); (c) `find_AD_firing_pattern` needs the whole input
|
||||
matrix resident and indexes by row (`98,116`), so a per-tick port must serialise each
|
||||
observation into a matrix row — cheap, but it is the kind of plumbing that hides latency; and
|
||||
(d) the 31.5 MB tensor if copied verbatim is a cache disaster (§2.3).
|
||||
|
||||
---
|
||||
|
||||
## 3. Should the 11 MB zip be committed or gitignored?
|
||||
|
||||
**Recommendation: commit it, under its explicit path `docs/BitBrain_C_code.zip`.** Facts
|
||||
behind that call `[MEASURED]`/`[VERIFIED]`:
|
||||
|
||||
- `git status` shows `docs/BitBrain_C_code.zip` as the only untracked file besides
|
||||
`common_libs/tests/measure_aim_vs_power.nim`, and there is no `.gitignore` rule covering it —
|
||||
consistent with the repo's stated convention of listing **explicit binary names**
|
||||
(`.gitignore` names `common_libs/tests/tm_measure`, `tr_bots/`, `*_garage/out/`, etc.) rather
|
||||
than broad `*.zip` patterns.
|
||||
- The repo's norms comfortably accommodate this size: it already tracks eleven ~11 MB zips
|
||||
(`SAC_LSTM_Bot_garage/weights*/sac_*.zip`) and PDFs up to 9.3 MB
|
||||
(`docs/papers/neuroevolution/2023_neuroevolution_recurrent_architectures.pdf`). `.git` is
|
||||
already 117 MB.
|
||||
- It is a **third-party primary source that cannot be re-derived** from anything in the repo —
|
||||
the weights, the thresholds, and the MNIST dump exist nowhere else here. If it is
|
||||
gitignored, every citation in this document becomes unverifiable for the next reader.
|
||||
Tracked `docs/papers/*.pdf` show the repo already treats third-party primary sources as
|
||||
trackable content.
|
||||
- Caveat, if the user would rather not carry it: the **explicit** rule to add is
|
||||
`docs/BitBrain_C_code.zip` (matching the existing style), and *also* `docs/BitBrain_C_code/`
|
||||
for the 55 MB extracted tree, which should never be committed in either case. Do not add
|
||||
`*.zip` — that would silently swallow the SAC weight archives.
|
||||
|
||||
---
|
||||
|
||||
## 4. Appendix — reproducing every measurement
|
||||
|
||||
```bash
|
||||
mkdir -p /tmp/bitbrain && cd /tmp/bitbrain
|
||||
unzip /home/davide/Projects/SirRoboGarage/docs/BitBrain_C_code.zip
|
||||
cd BitBrain_C_code
|
||||
gcc full_mnist_2048.c -O3 -lm -o bb && time ./bb # -> 346 wrong = 96.540 pct correct, 66 s
|
||||
```
|
||||
|
||||
Harnesses I wrote (throwaway, in `/tmp/bitbrain/measure/`, not in the repo):
|
||||
|
||||
| File | What it measures |
|
||||
|---|---|
|
||||
| `stats.c` | AD fire rates, 4-layer forward-pass timing |
|
||||
| `thresh.c` | per-ADE fire-rate distributions, sign/position statistics, dead-ADEs |
|
||||
| `exp.c` | online learning curve, SBC occupancy, dense vs sparse read timing |
|
||||
| `final.c` | shipped vs fixed vs sparse reader accuracy + per-sample cost on all 10,000 tests |
|
||||
| `dbg*.c` | the §1.6 truncation-bug isolation chain |
|
||||
|
||||
All three readers in `final.c` contain **verbatim copies** of `find_AD_firing_pattern` (98-124),
|
||||
`write_to_sbc` (129-152) and `read_from_sbc` (157-181); the only differences are the bug fix and
|
||||
the sparse traversal. The as-shipped reader reproduces the original program's 96.540% exactly,
|
||||
which is the check that the copies are faithful.
|
||||
Reference in New Issue
Block a user