The modern AI stack — in pure C you can read.
A real Transformer, on-device learning, and sentence embeddings. Zero dependencies. One header. Runs from a microcontroller to a browser tab.
Most neural network libraries are either massive frameworks or toy implementations. Nerve sits in between: small enough to read in an afternoon, correct enough for real work, portable enough to run anywhere C compiles — including bare-metal.
| Nerve | TensorFlow Lite | tinyML | plain C | |
|---|---|---|---|---|
| Zero dependencies | ✅ | ❌ | ❌ | ✅ |
| Single file | ✅ | ❌ | ❌ | — |
| Adam optimizer | ✅ | ✅ | ❌ | — |
| Dropout | ✅ | ✅ | ❌ | — |
| ANSI C89 compatible | ✅ | ❌ | ❌ | ✅ |
| < 1 500 lines | ✅ | ❌ | ❌ | — |
Nerve started as a multilayer perceptron. It now implements, from scratch and with zero dependencies, the pieces the giants run on — small enough to read, portable enough to run on a 2017 laptop or a browser tab. No GPU, no Python at runtime, no cloud, no per-token bill.
| It can… | How | Proof |
|---|---|---|
| Discover the equation behind your data | genetic programming over expression trees, with linear scaling, islands and a Pareto front, in one header | 82 of the 100 Feynman equations recovered to R² ≥ 0.999 on held-out data — bench/feynman |
| Run a real 1.1B-parameter LLM | decoder Transformer (RMSNorm, RoPE, GQA + KV-cache, SwiGLU, int8) in one header | TinyLlama-1.1B generates coherent text at ~3 tok/s on a laptop CPU — studies/infer |
| Learn from you, on-device | a frozen model's features + a tiny head trained with Nerve's own autodiff | personalises to your categories in ~40 ms, privately — studies/infer/learn.c |
| Understand meaning | a MiniLM BERT-style sentence encoder (int8, ~22 MB) | hamburger → food, running shoes → fitness; semantic search by meaning — studies/embed |
| Recognise handwriting | the 784-128-10 MLP trained on MNIST | ~97% test accuracy — studies/mnist |
| Run in your browser | the same C compiled to a 65 KB WebAssembly module | generate · teach · search · draw a digit — all client-side, nothing leaves the page — web/ |
| Differentiate anything | tape-based reverse-mode autodiff in ~200 lines | define a layer without hand-deriving its gradient — studies/autograd |
Every capability ships with a runnable proof, and every algorithm is grounded
in its paper — see docs/REFERENCES.md. Nerve does not try
to out-scale the giants; it earns its place by being the most readable,
portable, reproducible implementation of the same ideas.
Every other model in this repository answers a question with parameters.
nerve_discover.h answers it with a formula — one you
can read, check against theory, differentiate by hand, put in a paper, and
afterwards evaluate in nanoseconds, forever, for free.
#define NERVE_DISCOVER_IMPLEMENTATION
#include "nerve_discover.h"
nd_model *m = nd_fit(X, y, n, nvars, nd_defaults());
nd_print(nd_knee(m, 0.05), names, stdout);
nd_free(m);Hand it 200 noisy samples of a force, two masses and a distance, and it comes back with this — never having been told that gravity, squares or division are involved:
y = 0.996748*(m2*m1)/r^2 + 0.00943491
Hand it the nine planets as actually measured, and it returns Kepler's third
law in half a second. Hand it a Gaussian and it returns
0.398942*exp(-0.5*theta^2), where that leading constant is 1/√(2π) to six
figures.
How good is it? Measured, not asserted. The standard yardstick for this
field is the 100 equations of the Feynman Lectures — the accuracy track of
SRBench. Nerve recovers 82 of 100 to R² ≥ 0.999 on held-out data,
one run each, identical budget, no per-problem tuning. The benchmark is
in the repository, runs from a single make, downloads
nothing, and prints every failure alongside every success.
The result is not one equation but a Pareto front — the best formula at every complexity — so you can see precisely what each extra term bought, and where it stopped buying anything:
1 nodes R2 0.977612 y = 6.10996*a - 12.5517
2 nodes R2 0.987879 y = 0.161655*a^2 + 7.13717
4 nodes R2 0.99999951 y = 0.999498*a*sqrt(a) - 0.0274583 <- the knee
6 nodes R2 0.99999965 y = -1.00051*sqrt(a)*(0.0451932 - a) + 0.0384066
8 nodes R2 0.99999967 y = 1.00002*a*sqrt(sqrt(a^2 - -0.877334)) - 0.162988
21 nodes R2 0.99999969 y = 0.528091*((a/((a - 21.8141) - (a*sqrt(a)))) - ...
Paste a CSV, watch the equation evolve, get a formula. The whole engine is a 70 KB WebAssembly module with no data file at all — because an equation discoverer has no weights to download, the page is usable the instant it loads, works offline, and your data never leaves the tab.
Symbolic regression is normally a heavyweight scientific-Python affair: PySR is
a Python package over a Julia backend, AI Feynman needs a neural network. Those
are fine research tools, and none of them can be embedded. This one is a single
C99 header with no dependencies beyond libm — so it goes into an instrument,
a microcontroller, a desktop binary or a browser tab, which is exactly where
the interpreter cannot follow.
The same engine, compiled to WebAssembly, ships on npm — for the browser or Node. No server, no GPU, no API key; it runs on the user's machine.
npm install @fkkarakurt/nerveimport Nerve from "@fkkarakurt/nerve";
const nerve = await Nerve.load();
// 1. generate text (streams token by token)
nerve.generate("Once upon a time", { onToken: t => process.stdout.write(t) });
// 2. meaning similarity (cosine, -1..1)
nerve.similarity("a puppy on the grass", "a young dog in the park"); // ~0.5
// 3. learn your own categories, then classify
nerve.teach([
{ text: "schedule a meeting tomorrow", label: "calendar" },
{ text: "i want to eat a hamburger", label: "food" },
{ text: "go for a run in the park", label: "fitness" },
]);
console.log(nerve.classify("i want a pizza")); // { label: "food", confidence: 0.7, ... }
// 4. semantic search over your own notes — index() first, then search()
nerve.index([
"The capital of France is Paris.",
"Coffee contains caffeine, a stimulant.",
]);
console.log(nerve.search("what keeps me awake?")); // [{ text: "Coffee contains...", score }]Equation discovery is a separate entry point, because it needs no model at all — a ~62 KB module that starts instantly:
import { load, Ops } from "@fkkarakurt/nerve/discover";
const d = await load();
const { knee } = d.fromTable(planetsCsv, { ops: Ops.ALGEBRA });
console.log(knee.equation); // y = 0.999498*sqrt(a)*a - 0.0274583 (R² = 1.0)Text generation, on-device learning, semantic search and handwritten-digit recognition — all running client-side, in your tab. No server, no GPU, no API key, nothing uploaded. Documentation.
#define NERVE_IMPLEMENTATION
#include "nerve.h"
nerve_t *net = nerve_new("2->4->1"); /* Adam + Tanh + Xavier — sensible defaults */
nerve_fit(net, X, y, 4, 5000); /* train */
nerve_free(net);gcc -O2 main.c -o main -lm
No CMake. No vcpkg. No apt install. Just gcc and nerve.h.
#define NERVE_IMPLEMENTATION
#include "nerve.h"
network_t *net = net_allocate(3, 2, 8, 1);
net_set_optimizer(net, NERVENET_OPTIMIZER_ADAM);
net_set_activation(net, NERVENET_ACTIVATION_TANH);
net_initialize_xavier(net);
net_set_learning_rate(net, 0.01f);
net_set_l2_lambda(net, 1e-4f);
for (int i = 0; i < 5000; i++) {
int j = i % 4;
net_compute(net, in + j*2, NULL);
net_compute_output_error(net, tgt + j);
net_train(net);
}
net_free(net);Nerve never calls rand(). It carries its own generator — xoshiro128**
(Blackman & Vigna, 2021), seeded through SplitMix32 — because rand() cannot
give the same answer twice across platforms: it is implementation-defined, and
RAND_MAX is 32 767 on the Microsoft C runtime against 2 147 483 647 on
glibc. The same seed therefore produces different weights, at different
granularity, on Windows and Linux.
nerve_seed(42); /* same stream on every libc, compiler, target */
net_initialize_xavier(net); /* -> bit-identical weights, everywhere */The generator is 32-bit throughout, so it stays inside ANSI C89 and behaves
identically on a microcontroller, a laptop and a browser tab. Nerve is
deterministic by default — the state starts fixed, and you opt into variation
with nerve_seed(time(NULL)). nerve_rand_below(n) draws an unbiased integer
in [0, n) by rejection, not by a modulo that quietly favours low values.
The test suite checks Nerve's C89 generator against an exact-width reference implementation over 120 000 draws, and pins a golden vector so the stream can never drift silently.
Floating-point results still depend on the platform's
libm:exp()andtanh()are not correctly rounded by any standard, so training output can differ in the last digits across machines. What Nerve guarantees is that the random stream is never the reason two runs disagree.
Results on boolean functions (7 independent runs each):
XOR (2-4-1 network)
| Configuration | Converged | Avg Iters | Speedup |
|---|---|---|---|
| SGD / Uniform / Sigmoid | 7 / 7 | 22 285 | 1× |
| SGD / Xavier / Tanh | 7 / 7 | 13 647 | 1.6× |
| Adam / Xavier / Sigmoid | 7 / 7 | 2 601 | 8.6× |
| Adam / Xavier / Tanh | 7 / 7 | 2 183 | 10.2× |
| Adam / He / ReLU | 5 / 7 | 2 064 | 10.8× |
4-Class Identity (4-6-4 network)
| Configuration | Converged | Avg Iters | Speedup |
|---|---|---|---|
| SGD / Uniform / Sigmoid | 7 / 7 | 22 689 | 1× |
| Adam / He / ReLU | 7 / 7 | 1 214 | 18.7× |
| Component | Options | Reference |
|---|---|---|
| Optimizer | SGD + momentum | Rumelhart et al., 1986 |
| Optimizer | Adam ★ | Kingma & Ba, 2015 |
| Init | Xavier / Glorot | Glorot & Bengio, 2010 |
| Init | He ★ | He et al., 2015 |
| Activation | Sigmoid, Tanh, ReLU ★, Leaky ReLU | — |
| Regularisation | L2 weight decay | — |
| Regularisation | Dropout (inverted) | Srivastava et al., 2014 |
★ recommended combination for hidden layers
Eleven standalone .c files — each compiles with a single gcc command.
Core examples
| # | File | Result |
|---|---|---|
| 01 | 01_xor.c |
XOR in < 2 000 iterations (Adam) |
| 02 | 02_sine.c |
sin(x) approximation, MSE < 0.00002 |
| 03 | 03_iris.c |
Iris classification 96.7% test accuracy |
| 05 | 05_regression.c |
Auto MPG regression, RMSE 3.63 mpg |
| 06 | 06_dropout.c |
Dropout generalisation +7 pp over no-dropout |
| 07 | 07_spiral.c |
3-class non-linear spiral 98.3% accuracy |
| 08 | 08_model_io.c |
Save → load → fine-tune checkpoint workflow |
| 09 | 09_predictive_maintenance.c |
Live sensor monitor, 100% test accuracy |
Terminal AI games — neural networks that learn to play, live in your terminal
| # | File | Architecture | Algorithm |
|---|---|---|---|
| 10 | 10_snake_ai.c |
11 → 16 → 3 | Neuroevolution, pop 100 |
| 11 | 11_pong_ai.c |
5 → 8 → 1 | Neuroevolution vs rule-based bot |
| 12 | 12_flappy_ai.c |
5 → 8 → 1 | 20 birds evolving simultaneously |
Example outputs
$ gcc -O2 examples/01_xor.c -o xor -lm && ./xor
Nerve 2.0.0 — XOR Example
Architecture: 2-4-1 | Adam | Xavier Init
Results after 1847 epochs:
[1, 1] 0.0 → 0.0031 OK
[1, 0] 1.0 → 0.9968 OK
[0, 1] 1.0 → 0.9967 OK
[0, 0] 0.0 → 0.0028 OK
$ gcc -O2 examples/03_iris.c -o iris -lm && ./iris
Final test accuracy: 96.7%
Confusion Matrix:
setosa versicolor virginica
setosa 8 0 0
versicolor 0 13 0
virginica 0 0 9
$ gcc -O2 examples/07_spiral.c -o spiral -lm && ./spiral
Final Test Accuracy: 98.3% (59 / 60)
+--------------------------------------------------------------+
|XXXXXXXXXXXX..................................................|
|XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX........................|
|XXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOXXXXXXXXXXX................|
|XXXXXXXXXXOOOOOOO........XXXX.......OOOOOOXXXXXXXXX...........|
|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|
+--------------------------------------------------------------+
Reading 1 → [ OK ] NORMAL 99.9%
Reading 5 → [ WARN ] WARNING 98.8%
Reading 8 → [ CRIT ] CRITICAL 97.3%
Reading 11 → [FAULT ] FAULT 99.5%
Easy API
/* Create */
nerve_t *net = nerve_new("2->8->1"); /* Adam + Tanh + Xavier */
nerve_config_t cfg = nerve_default_config();
cfg.lr = 0.005f;
cfg.dropout = 0.2f;
nerve_t *net = nerve_new_ex("4->32->3", &cfg);
/* Train */
nerve_fit(net, X, y, n_samples, epochs);
nerve_fit_verbose(net, X, y, n_samples, epochs, 100);
/* Evaluate */
float acc = nerve_score(net, X, y, n_samples);
nerve_predict(net, x, output);
int cls = nerve_classify(net, x);
/* Persist */
nerve_save(net, "model.net");
nerve_t *loaded = nerve_load("model.net");
nerve_free(net);Core API
/* Determinism */
nerve_seed(42); /* seed the built-in generator */
float u = nerve_rand_float(); /* uniform [0,1) */
unsigned long k = nerve_rand_below(10); /* unbiased integer in [0,10) */
/* Allocate */
network_t *net = net_allocate(3, 64, 128, 10);
net_free(net);
/* Configure */
net_set_activation(net, NERVENET_ACTIVATION_TANH);
net_set_optimizer(net, NERVENET_OPTIMIZER_ADAM);
net_initialize_xavier(net);
net_set_learning_rate(net, 0.01f);
net_set_momentum(net, 0.9f);
net_set_l2_lambda(net, 1e-4f);
net_set_dropout(net, 0.2f);
/* Train — one sample */
net_compute(net, input, NULL);
net_compute_output_error(net, target);
net_train(net);
/* Train — shuffled epoch */
float mse = net_train_epoch(net, inputs, targets,
n_samples, n_inputs, n_outputs, batch_size);
/* Inference */
float out[10];
net_compute(net, input, out);
int label = net_classify(net, input);
float acc = net_compute_accuracy(net, inputs, targets, n, n_in, n_out);
int cm[9] = {0};
net_confusion_matrix(net, inputs, targets, n, n_in, n_out, 3, cm);
/* Persist — filename first, then the network */
net_save("model.net", net); network_t *net = net_load("model.net");
net_bsave("model.bin", net); network_t *net = net_bload("model.bin");After loading, re-apply
net_set_activation()andnet_set_optimizer().
# All examples (Linux / macOS)
make -C examples
# Terminal games only
make -C examples games
# CMake (Linux / macOS / Windows)
cmake -B build && cmake --build build
# Individual example
gcc -O2 examples/01_xor.c -o xor -lmgcc -O2 -std=c99 -Wall -Wextra tests/test_nerve.c -o test_nerve -lm && ./test_nerve
gcc -O2 -std=c99 -Wall -Wextra tests/test_discover.c -o test_discover -lm && ./test_discoverOne file each, no framework, no dependencies — the same one-line build as everything else here. The core suite checks the generator against a reference implementation, verifies the backprop gradients against central finite differences, and round-trips every persistence format. The discovery suite pins the structural invariants of the Pareto front, determinism under a fixed seed, buffer safety in the formatter, and behaviour on degenerate input. CI runs both on Linux (GCC and Clang), macOS and Windows, under ASan + UBSan, and separately enforces that the core still compiles as strict ANSI C89 — the standards claim above is a build failure if it ever stops being true, not a line of marketing.
make -C bench/feynman full # 100 equations, no downloadClaims in this README that carry a number are produced by something in the
repository that you can re-run. The equation-discovery figure comes from
bench/feynman/, whose protocol — budget, operator set,
train/test split, and what counts as solved — is written down rather than
implied.
Nerve is a from-scratch implementation of established results, written to be
read. Every component — attention, RoPE, RMSNorm, SwiGLU, GELU, BERT-style
encoders, Adam, dropout, autodiff, int8 quantization, and the genetic
programming, linear scaling and Pareto selection behind the discovery engine —
is grounded in its original paper in
docs/REFERENCES.md. The contribution is readability,
portability and reproducibility, not new science.
@software{nerve,
author = {Fatih Küçükkarakurt},
title = {{Nerve: Technical Reference Manual — A Zero-Dependency Single-Header Multilayer Perceptron Library for ANSI C}},
year = 2026,
publisher = "Nerve Developer",
doi = {10.5281/zenodo.20432307},
url = {https://doi.org/10.5281/zenodo.20432307}
}Copyright 2022-2026 Fatih Küçükkarakurt
Released under the Apache License 2.0 — permissive, with an express patent grant. Embed it in commercial and closed-source products; keep the notice, and it's yours to ship.