Skip to content

Repository files navigation

Nerve

Flappy Bird AI learning in real-time via neuroevolution

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.

CI License: Apache-2.0 Language Single Header Zero Deps Version npm PRs Welcome

Platforms

DOI

📖 Documentation & Scientific Manual


Why Nerve?

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

Beyond a library — the modern AI stack, in C you can read

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.


Give it numbers. Get back an equation.

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.


Use it from JavaScript / TypeScript

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

npm install @fkkarakurt/nerve
import 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.


Quick Start

3-line API

#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.

Full-control API

#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);

Reproducible by construction

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() and tanh() 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.


Benchmark — SGD vs Adam

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
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
Adam / He / ReLU 7 / 7 1 214 18.7×

Algorithm Reference

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


Examples

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

XOR

$ 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

Iris Classification

$ 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

3-Class Spiral

$ gcc -O2 examples/07_spiral.c -o spiral -lm && ./spiral

  Final Test Accuracy: 98.3%  (59 / 60)

  +--------------------------------------------------------------+
  |XXXXXXXXXXXX..................................................|
  |XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX........................|
  |XXXXXXXXXXXXXXXXXXXXXXXXXOOOOOOOOOOXXXXXXXXXXX................|
  |XXXXXXXXXXOOOOOOO........XXXX.......OOOOOOXXXXXXXXX...........|
  |OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|
  +--------------------------------------------------------------+

Predictive Maintenance

  Reading  1   → [  OK  ] NORMAL    99.9%
  Reading  5   → [ WARN ] WARNING   98.8%
  Reading  8   → [ CRIT ] CRITICAL  97.3%
  Reading 11   → [FAULT ] FAULT     99.5%

API Reference

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() and net_set_optimizer().


Building

# 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 -lm

Tests

gcc -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_discover

One 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.

Benchmarks

make -C bench/feynman full     # 100 equations, no download

Claims 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.

Academic foundations

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.

Citation

@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}
}

License

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.

About

Discover the equation behind your data — 82/100 Feynman equations, from one dependency-free C header. Plus a real Transformer, embeddings and on-device learning.

Topics

Resources

Stars

56 stars

Watchers

1 watching

Forks

Releases

Contributors

Languages