See the NN-Revival series page for methodology, scope, and other entries. Note #1 (Weightless Neural Networks on Modern Compute) frames the hypothesis this note tests.

Abstract

Note #1 found that vanilla WiSARD degrades past its capacity knee — at N=10k accuracy falls (0.877 → 0.847) — and framed the fix as sparse, specialized addressing. CMAC (Albus, 1975) is that fix in its original form: an input activates a small fixed number C of memory cells via overlapping quantization tilings; the output is their sum; learning is a local delta rule on only those C cells, O(C) per example, no backprop. We test it faithfully in its native domain — low-dimensional function approximation and open-loop control — with a labeled, out-of-domain MNIST comparability arm, under the same protocol as #1: three seeds, natural budgets, predictions pre-registered and frozen before runs.

The headline holds within the range we tested: across C from 4 to 256, adding capacity never degraded CMAC's quality — it improved and then plateaued (0.0379 → 0.0113 RMSE), where WiSARD's N-scaling actively reverses at N=10k. We did not find a saturation cliff on CMAC's capacity axis; we do not claim none exists beyond what we swept. CMAC's one sharp failure — over-fine tiles for the data budget — is undersampling, not interference: it recovers fully with more data (0.123 → 0.0036 as n grows 4k → 32k), a failure mode WiSARD's intrinsic cliff does not share. The costs are honest: sample efficiency is local, so a small MLP wins the ultra-low-data regime and CMAC only crosses ahead once the domain is covered (n ≥ 1000 on both native tasks); and the memory footprint is large (~130× the MLP's parameters at the collision floor). Online, CMAC tracks a shifted target cheaply and without replay (0.190 vs its own frozen 0.754). This is a toy-scale study; on 784-d MNIST, out of CMAC's native domain, a plain MLP wins (0.972 vs 0.950). Not a SOTA claim.

Why

Note #1 left an explicit hypothesis on the table: WiSARD saturates because every input touches the same fixed bank of RAMs at scale, and a sparse/specialized addressing scheme — routing each input to a small, input-dependent subset of cells — might avoid it. CMAC is the cleanest 1975-vintage instance of exactly that idea, and it was built for a concrete job: low-dimensional function approximation and robot control. So #2 is not a fresh survey; it is the direct empirical follow-through on #1's predicted fix, tested in the domain CMAC was actually designed for.

We do not claim a new architecture. CMAC is prior art. The tiling/hashing/local-delta machinery is Albus's. Our contribution is the honest, controlled measurement: does the sparse-addressing story hold, and at what cost?

Scope

This series re-examines architectures in their original form under modern controls. The faithful anchor must be in the architecture's native domain. CMAC's native domain is low-dimensional continuous function approximation and control — so the faithful arms are a 2-D nonlinear surface and an open-loop 2-link inverse-kinematics map. MNIST (784-d binary) is not native to CMAC; it appears only as a labeled, secondary comparability arm using a hashing adaptation, carried so the series has a common yardstick against #1's WiSARD and MLP. Rigor and defensibility concentrate on the native anchors; the MNIST arm is explicitly out-of-domain and is not where CMAC is meant to win.

Setup

Faithful CMAC (native arms). C overlapping quantization tilings on the continuous input; tiling t offset by (t/C) · tile_width on every dimension. Each input falls in exactly one tile per tiling → C active cells. Tile coordinates are hashed into a bounded table for memory only. Output = sum of the C active weight vectors; update: w_a += (η/C) · (target − output) on those C cells. Natural native budget: C=32, tile_width=0.05, table=8192, η=0.35. No bit-hashing is used on the native tasks.

MNIST arm (out-of-domain, labeled). A hashing-CMAC: C fixed random bit-subsets of the 784-d input, each FNV-hashed to a bounded table → C active cells → sum → local delta. This is a sparse-code-by-construction adaptation, not faithful tiling; it carries no native-domain claim.

Baselines. A small MLP (native: one hidden layer h=64, lr=0.05, plain Rust, no framework). MNIST comparability additionally carries wisard and mlp from #1, run here under the same protocol; the MNIST wisard uses #1's bleach-threshold search (a no-bleach variant was archived and is not cited — it collapses full-data WiSARD to 0.671, which is a broken configuration, not a result).

Protocol. Seeds {0, 1, 2}; mean ± range with range = (max − min)/2. Native runs use early-stop on validation RMSE (patience 10, max 80 epochs) and report best-val, not final epoch. Predictions written in SPEC.md before the runs and never edited afterward — outcomes append only.

Faithfulness gate. Before any native task ran, a geometric probe confirmed local generalization emerges from the tiling (below). This is the check that the native arm is faithful CMAC and not random hashing wearing tiling's name.

Results

All figures below are generated from frozen artifacts: results/stage2_full_frozen/, results/stage3_mnist_frozen/, results/stage4_full_frozen/, results/stage4_n4_control_frozen/.

Faithfulness gate — local generalization (pre-hash tile overlap)

Two inputs at Euclidean distance d; mean shared active tiles out of C=16, measured on tile coordinates (pre-hash) so hash collisions cannot fake sharing.

distancedist / tile_wmean shared / 16
0.0000.016.00
0.0100.113.92
0.0250.2511.08
0.0500.57.23
0.0900.91.86
0.1001.00.83
≥0.150≥1.50.00

Monotone falloff, hitting zero by ~1.5 tile widths. Values sit just below the 1-D stagger line C · max(0, 1 − d/w) because in 2-D a tile is shared only when both axes stay in-tile — the correct direction for the discrepancy. Local generalization is real and tile-width-scaled. Gate: PASS.

Q1 — native sample-efficiency (held-out RMSE, 3 seeds)

taskarmn=50n=200n=1000n=4000paramsactive
fn_approxcmac0.4502±0.00360.3442±0.01210.1262±0.00320.0175±0.0047262,14432
fn_approxmlp0.4876±0.00430.3778±0.07180.1550±0.00580.1465±0.0004257
ikcmac1.5635±0.01121.1420±0.01730.3978±0.03600.1697±0.0222524,28832
ikmlp0.8079±0.03960.5901±0.04390.4981±0.08490.3705±0.0343322

The crossover is real and is not smoothed. On IK, the MLP wins the low-data regime outright (n=50, 200) and CMAC crosses ahead only at n ≥ 1000. On fn_approx the mean shows CMAC ahead throughout, but at n=200 the per-seed picture is mixed — the MLP wins seeds 0 and 1, and its large range (±0.0718) reflects a single-seed failure that flatters CMAC's mean. The honest, defensible crossover for both native tasks is n ≥ 1000. Once the domain is covered, CMAC pulls far ahead: fn_approx reaches RMSE 0.0175 on a signal of scale ≈0.4, and IK reaches 0.170 rad (≈9.7°). Active cells are exactly C=32 on every row.

Q2 — capacity (held-out RMSE, native fn_approx, n=4000)

Ctile_wtest RMSEactiveparams
80.1000.02197±0.000258131,072
160.1000.01472±0.0002716262,144
320.1000.01213±0.0002432524,288
320.0500.01701±0.0033132524,288
640.0500.01700±0.00328641,048,576
640.0250.12317±0.00139641,048,576
1280.0250.12305±0.001181282,097,152

#1 WiSARD N-scaling, on the same "add capacity → watch quality" axis (accuracy, cited from ramnet-study):

Nacc
1000.8477±0.0064
5000.8713±0.0013
10000.8769±0.0002
50000.8759±0.0039
100000.8474±0.0051

Along the C axis at fixed width, adding capacity never degrades CMAC — it improves and then plateaus. WiSARD, on its capacity axis, degrades past the knee: N=10k falls back to N=100 accuracy. But note the two rows where CMAC does cliff — width 0.025 jumps to 0.123. That is not a capacity-interference cliff; the control below shows it is coverage.

Q2 control — the fine-tile cliff is coverage, not saturation

(A) Fine tiles recover with data (C=64, tile_w=0.025):

n_traintest RMSE
5000.38283±0.00104
10000.31557±0.00656
20000.21439±0.00842
40000.11783±0.01302
80000.04031±0.00354
160000.01350±0.00200
320000.00356±0.00038

(B) Adding tilings never hurts (fixed tile_w=0.10, n=4000):

Ctest RMSE
40.03786±0.00124
80.02234±0.00065
160.01488±0.00033
320.01237±0.00035
640.01166±0.00040
1280.01135±0.00033
2560.01132±0.00035

The fine-tile cliff at width 0.025 fully recovers as data grows (0.118 at n=4k → 0.0036 at n=32k) — it is the same local-generalization coverage limit as Q1's low-n regime: finer tiles cover less domain per tile, so they need more samples to fill. Cure it with data and it vanishes. WiSARD's N=10k degradation does not cure with data — it is intrinsic interference. This is the study's central result: across the capacity range we swept, sparse tiled addressing shows no interference-saturation. Adding capacity never cost quality; the only sharp failure was orthogonal (undersampling) and data-curable.

Q3 — hash collisions (native fn_approx, C=32, n=4000)

table_sizetest RMSEparams
640.39272±0.004172,048
1280.35990±0.004194,096
2560.24533±0.002448,192
5120.18649±0.0021716,384
10240.01310±0.0015132,768
20480.01310±0.0015165,536
40960.01310±0.00151131,072
81920.01310±0.00151262,144
163840.01310±0.00151524,288

Collisions bite hard below 1024 bins/tiling and vanish at/above it — flat through 16k. The collision floor sits at ~33k parameters (1024 × C). The natural-budget table used in Q1 (262k params) was ~8× larger than needed. So CMAC's real memory penalty on this task is ~130× the MLP's 257 floats, not the ~1000× the naive budget implied — still a large footprint, but the honest number.

Q4 — online adaptation (native, RMSE across a mid-stream target shift)

armpremid (post-shift)post-onlinepost-frozen
cmac0.0149±0.00100.3835±0.01240.1904±0.01910.7540±0.0116
mlp0.1577±0.00280.3192±0.04750.3021±0.02030.7450±0.0184

The target is shifted mid-stream; both learners then adapt online. CMAC recovers to 0.190 via cheap local updates (O(C), no replay) versus its own frozen-weights control at 0.754. The MLP also adapts (0.302 vs its frozen 0.745) — this is not "only CMAC can" — but CMAC adapts further and more cheaply. Online tracking with local writes is a genuine CMAC edge, not a unique capability.

MNIST comparability (out-of-domain, labeled)

armk=1k=5k=10k=50k=100fullparams
hash_cmac0.3552±0.01210.5872±0.00890.7002±0.01830.8370±0.00680.8731±0.00240.9496±0.00125,242,880
wisard0.3979±0.01340.6614±0.02190.7706±0.02770.8629±0.00700.8696±0.00360.8749±0.00170
mlp0.3741±0.03280.6630±0.00300.7517±0.02490.8526±0.00910.8739±0.00250.9718±0.001350,890

Off native turf, the MLP wins full-data (0.972), hash-CMAC is second (0.950), and bleached WiSARD trails at full data (0.875) — but WiSARD leads the few-shot regime (k=5, k=10). Hash-CMAC's footprint here is large (5.2M) and it is a sparse random code, not a tiling.

Scorecard (pre-registered → outcome; predictions never edited)

Native anchors (the faithful priority):

#PredictionVerdict
N1Approximates native tasks well; competitive with a small MLPSUPPORTED — beats MLP at n ≥ 1000 on both tasks; low absolute error (fn 0.0175; IK 0.170 rad at n=4000).
N2Sample-efficient (low error from few samples)PARTIAL MISS — contradicted below coverage: MLP wins ultra-low-n (IK n=50/200; fn n=200 per-seed mixed). Real finding: generalization is local, so CMAC crosses ahead only once the domain is covered (n ≥ 1000).
N3Online adaptation tracks a changing function via cheap local updatesSUPPORTED — recovers to 0.190 post-shift (vs frozen 0.754), O(C), no replay. Caveat: the MLP also adapts (0.302), just less and dearer.
N4Capacity scaling shows a softer plateau than WiSARDSUPPORTED — adding C never degrades (0.0379 → 0.0113, monotone), where WiSARD's N=10k reverses. The one sharp CMAC failure (over-fine tiles) is coverage, not interference: it fully recovers with data (0.123 → 0.0036).
N5Active cells per example stays O(C) regardless of capacitySUPPORTED (exact) — active ≡ C on every configuration.

MNIST comparability (secondary, out-of-domain):

#PredictionVerdict
M1CMAC < MLP on MNIST (out of native domain)TRUE — 0.9496 < 0.9718.
M2Local generalization weakened on 784-d → behaves like a random sparse codeCONSISTENT, NOT INDEPENDENTLY TESTED — the MNIST arm is a hashing sparse-code by construction (no tiling), so it cannot lose a local generalization it never had. Consistent with M2, but not evidence for it.
M3vs WiSARD: sparse-tiled vs n-tuple addressing — direction openRESOLVED (direction): low-data → WiSARD (k=5: 0.661 vs 0.587; k=10: 0.771 vs 0.700); full-data → MLP > hash-CMAC > WiSARD (0.972 > 0.950 > 0.875).

Predictions that came out a PARTIAL MISS (N2) or not independently testable (M2) are left in the scorecard as written. No retcon.

What it's good for

CMAC earns its keep exactly where 1975 aimed it:

  • Low-dimensional function approximation and control, where the input space can actually be covered — then local generalization gives very low error at O(C) update cost.
  • Online / nonstationary settings: track a drifting target with cheap local writes, no backprop, no replay buffer.
  • Capacity you can add safely: unlike WiSARD, more tilings never hurt quality — you tune C and width for coverage, not to dodge a saturation cliff.

It is not for high-dimensional perception. On 784-d MNIST it needs a hashing adaptation, loses its smooth-interpolation story, carries a huge table, and still loses to a small MLP. Curse of dimensionality is the boundary of its native domain, and it shows.

Limitations

  • Toy scale, small baselines. The MLP baselines are one-hidden-layer nets; "beats the MLP" means at matched small scale, not versus a modern deep net.
  • Native tasks are synthetic. A fixed 2-D surface and an open-loop 2-link IK map — chosen to be genuinely native, not to be hard. Open-loop only; no closed-loop dynamics.
  • Memory footprint is large. Even at the collision floor (~33k params) CMAC is ~130× the native MLP's parameter count. Its efficiency claim is compute per example (O(C), ~1 µs/update), not memory.
  • Sample efficiency is local, not global. The headline N2 caveat: CMAC needs the domain covered; it is not a few-shot learner in low dimensions the way the pre-registration guessed.
  • MNIST arm is out-of-domain by construction. Hashing-CMAC there is a labeled comparability stretch, not a faithful measurement.

Series note

Note #2 closes the loop #1 opened: the sparse/specialized addressing #1 predicted as the fix for WiSARD's N-scaling saturation does remove the interference cliff, at the cost of a large table and a locality constraint on sample efficiency. Banked for a later hybrid-CMAC rethink is prediction P7 — that a static+plastic CMAC will show the same readout-domination #1's hybrid did, the plastic channel over-saturating the shared sum unless explicitly balanced. The shared harness across #1 and #2 will be extracted now that two entries exist to reveal what is actually common (not before). Next in the backlog: Cascade-Correlation (#3) — a different mechanism (grow-on-demand structure) against the same adaptive-capacity thesis.

Reproducibility

  • Code: github.com/Vulkgryph/cmac-study — package cmac-study under NN-Revival.
  • Spec: SPEC.md — predictions frozen 2026-08-12 before the runs; outcomes append-only.
  • Frozen results (do not overwrite):
    • results/stage2_full_frozen/ — Q1 native sample-efficiency + local-gen probe, 3 seeds
    • results/stage3_mnist_frozen/ — MNIST comparability (bleached WiSARD), 3 seeds
    • results/stage4_full_frozen/ — Q2 capacity / Q3 collisions / Q4 online
    • results/stage4_n4_control_frozen/ — the coverage-vs-saturation control

SHA-256 of frozen JSON (verify with the command below):

FileSHA-256
stage2_full_frozen/summary.jsond41c756fbe19b22706c639b8e6f0a28ac9ade27be424e100d877a5bbde6b3603
stage2_full_frozen/local_gen_probe.json8ca8b474bbad7ad034a86ff56503d4f7868cb20ee889cbcc19292cc6b434d698
stage3_mnist_frozen/summary.json1c818cbb49c635b9d3451c7d078f9bf889b57251970d0c93a8aa26646f2a51c8
stage4_full_frozen/summary.jsonab7fe382f4ff0ac72f4dc6624d1dd2419268d6ac3cc1bfad5866a8da4904acd1
stage4_n4_control_frozen/summary.jsona211d0ef4a2d3beffbc200e6d7ca179506d1215115c25e83daa59991a8bfe2a0
shasum -a 256 \
  cmac-study/results/stage2_full_frozen/summary.json \
  cmac-study/results/stage2_full_frozen/local_gen_probe.json \
  cmac-study/results/stage3_mnist_frozen/summary.json \
  cmac-study/results/stage4_full_frozen/summary.json \
  cmac-study/results/stage4_n4_control_frozen/summary.json

Tables: every figure above is generated from the frozen JSON by cmac-study/gen_tables.pyresults/GENERATED_TABLES.md. No result numbers are hand-typed.