Skip to content

Instantly share code, notes, and snippets.

@nmicic
Forked from karpathy/microgpt.py
Last active March 23, 2026 16:43
Show Gist options
  • Select an option

  • Save nmicic/35316463f3c5e8e9fe8eb599b3842b58 to your computer and use it in GitHub Desktop.

Select an option

Save nmicic/35316463f3c5e8e9fe8eb599b3842b58 to your computer and use it in GitHub Desktop.
microgpt.c — sub-20ms GPT training and inference in pure C
/* Copyright (c) 2026 Nenad Mićić <nenad@micic.be> https://be.linkedin.com/in/nenadmicic
* SPDX-License-Identifier: Apache-2.0
*
* microgpt.c — Minimal GPT training and inference in 655 lines of pure C.
* Hand-written forward and backward pass, two-phase gradient accumulation.
* Trains a 1-layer, 4-head, 4192-parameter character-level transformer.
*
* Inspired by Andrej Karpathy's microgpt.py (200 lines of Python):
* https://gist.github.com/karpathy/8627fe009c40f57531cb18360106ce95
*
* Single-file, no dependencies beyond libc + libm.
* Compile: cc -O2 -Wall -Wextra -ffp-contract=off -o microgpt microgpt.c -lm
*
* Dataset: 32K names from karpathy/makemore (lowercase a-z only):
* https://raw.githubusercontent.com/karpathy/makemore/master/names.txt
* Save as names.txt in the same directory.
*
* Performance: ~19 ms on a MacBook Air M3 for 1000 training steps + 20 samples,
* compiled with cc -O2 -Wall -Wextra -ffp-contract=off. * Bit-exact output match with the Python reference.
*
* For datasets with characters beyond a-z, the tokenizer needs to be
* made dynamic (build vocab from sorted unique chars at runtime).
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
/* ── Model constants ─────────────────────────────────────────────── */
#define N_EMBD 16
#define N_HEAD 4
#define HEAD_DIM (N_EMBD / N_HEAD)
#define BLOCK_SIZE 16
#define N_LAYER 1
#define MLP_DIM (4 * N_EMBD)
#define VOCAB_SIZE 27
#define NUM_PARAMS 4192
#define MAX_DOCS 33000
#define MAX_NAME 32
/* ── MT19937 RNG ─────────────────────────────────────────────────── */
#define MT_N 624
#define MT_M 397
#define MT_MATRIX_A 0x9908b0dfU
#define MT_UPPER 0x80000000U
#define MT_LOWER 0x7fffffffU
static uint32_t mt[MT_N];
static int mt_idx = MT_N + 1;
static void mt_init_genrand(uint32_t s) {
mt[0] = s;
for (int i = 1; i < MT_N; i++)
mt[i] = 1812433253U * (mt[i-1] ^ (mt[i-1] >> 30)) + (uint32_t)i;
mt_idx = MT_N;
}
static void mt_init_by_array(const uint32_t *key, int keylen) {
mt_init_genrand(19650218U);
int i = 1, j = 0;
int k = MT_N > keylen ? MT_N : keylen;
for (; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525U))
+ key[j] + (uint32_t)j;
i++; j++;
if (i >= MT_N) { mt[0] = mt[MT_N-1]; i = 1; }
if (j >= keylen) j = 0;
}
for (k = MT_N - 1; k; k--) {
mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941U))
- (uint32_t)i;
i++;
if (i >= MT_N) { mt[0] = mt[MT_N-1]; i = 1; }
}
mt[0] = 0x80000000U;
mt_idx = MT_N;
}
static uint32_t mt_genrand(void) {
static const uint32_t mag01[2] = {0U, MT_MATRIX_A};
if (mt_idx >= MT_N) {
int kk;
for (kk = 0; kk < MT_N - MT_M; kk++) {
uint32_t y = (mt[kk] & MT_UPPER) | (mt[kk+1] & MT_LOWER);
mt[kk] = mt[kk + MT_M] ^ (y >> 1) ^ mag01[y & 1U];
}
for (; kk < MT_N - 1; kk++) {
uint32_t y = (mt[kk] & MT_UPPER) | (mt[kk+1] & MT_LOWER);
mt[kk] = mt[kk + (MT_M - MT_N)] ^ (y >> 1) ^ mag01[y & 1U];
}
{
uint32_t y = (mt[MT_N-1] & MT_UPPER) | (mt[0] & MT_LOWER);
mt[MT_N-1] = mt[MT_M-1] ^ (y >> 1) ^ mag01[y & 1U];
}
mt_idx = 0;
}
uint32_t y = mt[mt_idx++];
y ^= (y >> 11);
y ^= (y << 7) & 0x9d2c5680U;
y ^= (y << 15) & 0xefc60000U;
y ^= (y >> 18);
return y;
}
static double rng_double(void) {
uint32_t a = mt_genrand() >> 5;
uint32_t b = mt_genrand() >> 6;
return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
}
static double gauss_next_val;
static int gauss_has_next = 0;
static double rng_gauss(double mu, double sigma) {
double z;
if (gauss_has_next) {
z = gauss_next_val;
gauss_has_next = 0;
} else {
double x2pi = rng_double() * (2.0 * M_PI);
double g2rad = sqrt(-2.0 * log(1.0 - rng_double()));
z = cos(x2pi) * g2rad;
gauss_next_val = sin(x2pi) * g2rad;
gauss_has_next = 1;
}
return mu + z * sigma;
}
static uint32_t rng_getrandbits(int k) {
return mt_genrand() >> (32 - k);
}
static int bit_length(uint32_t n) {
int b = 0;
while (n) { b++; n >>= 1; }
return b;
}
static int rng_randbelow(int n) {
int k = bit_length((uint32_t)n);
int r = (int)rng_getrandbits(k);
while (r >= n) r = (int)rng_getrandbits(k);
return r;
}
static void rng_shuffle(int *arr, int len) {
for (int i = len - 1; i >= 1; i--) {
int j = rng_randbelow(i + 1);
int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;
}
}
static int rng_weighted_choice(const double *weights, int n) {
double cum[VOCAB_SIZE];
cum[0] = weights[0];
for (int i = 1; i < n; i++) cum[i] = cum[i-1] + weights[i];
double total = cum[n-1];
double target = rng_double() * total;
int lo = 0, hi = n - 1;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (target < cum[mid]) hi = mid;
else lo = mid + 1;
}
return lo;
}
/* ── Data ────────────────────────────────────────────────────────── */
static char docs[MAX_DOCS][MAX_NAME];
static int doc_idx[MAX_DOCS];
static int num_docs;
static void load_data(void) {
FILE *f = fopen("names.txt", "r");
if (!f) { fprintf(stderr, "Cannot open names.txt\n"); exit(1); }
char buf[256];
num_docs = 0;
while (fgets(buf, sizeof(buf), f)) {
int len = (int)strlen(buf);
while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r' || buf[len-1] == ' '))
buf[--len] = '\0';
if (len == 0) continue;
if (num_docs >= MAX_DOCS) { fprintf(stderr, "Too many docs\n"); exit(1); }
if (len >= MAX_NAME) len = MAX_NAME - 1;
memcpy(docs[num_docs], buf, len);
docs[num_docs][len] = '\0';
doc_idx[num_docs] = num_docs;
num_docs++;
}
fclose(f);
}
/* ── Model parameters ────────────────────────────────────────────── */
/*
* Layout (row-major):
* wte: VOCAB_SIZE x N_EMBD = 27*16 = 432
* wpe: BLOCK_SIZE x N_EMBD = 16*16 = 256
* lm_head: VOCAB_SIZE x N_EMBD = 27*16 = 432
* wq: N_EMBD x N_EMBD = 16*16 = 256
* wk: N_EMBD x N_EMBD = 16*16 = 256
* wv: N_EMBD x N_EMBD = 16*16 = 256
* wo: N_EMBD x N_EMBD = 16*16 = 256
* fc1: MLP_DIM x N_EMBD = 64*16 = 1024
* fc2: N_EMBD x MLP_DIM = 16*64 = 1024
* Total: 4192
*/
static double params[NUM_PARAMS];
static double grads[NUM_PARAMS];
static double adam_m[NUM_PARAMS];
static double adam_v[NUM_PARAMS];
#define OFF_WTE 0
#define OFF_WPE (OFF_WTE + VOCAB_SIZE * N_EMBD)
#define OFF_LMHEAD (OFF_WPE + BLOCK_SIZE * N_EMBD)
#define OFF_WQ (OFF_LMHEAD + VOCAB_SIZE * N_EMBD)
#define OFF_WK (OFF_WQ + N_EMBD * N_EMBD)
#define OFF_WV (OFF_WK + N_EMBD * N_EMBD)
#define OFF_WO (OFF_WV + N_EMBD * N_EMBD)
#define OFF_FC1 (OFF_WO + N_EMBD * N_EMBD)
#define OFF_FC2 (OFF_FC1 + MLP_DIM * N_EMBD)
#define W(off, r, c, cols) params[(off) + (r) * (cols) + (c)]
#define G(off, r, c, cols) grads[(off) + (r) * (cols) + (c)]
/* ── Forward-pass intermediates ──────────────────────────────────── */
static double fwd_emb [BLOCK_SIZE][N_EMBD];
static double fwd_x0 [BLOCK_SIZE][N_EMBD];
static double fwd_x0_scale[BLOCK_SIZE];
static double fwd_xr1 [BLOCK_SIZE][N_EMBD];
static double fwd_xn1 [BLOCK_SIZE][N_EMBD];
static double fwd_xn1_sc [BLOCK_SIZE];
static double fwd_q [BLOCK_SIZE][N_EMBD];
static double fwd_k [BLOCK_SIZE][N_EMBD];
static double fwd_v [BLOCK_SIZE][N_EMBD];
static double fwd_attn_w [BLOCK_SIZE][N_HEAD][BLOCK_SIZE];
static double fwd_xa [BLOCK_SIZE][N_EMBD];
static double fwd_x2 [BLOCK_SIZE][N_EMBD];
static double fwd_xn2 [BLOCK_SIZE][N_EMBD];
static double fwd_xn2_sc [BLOCK_SIZE];
static double fwd_fc1pre [BLOCK_SIZE][MLP_DIM];
static double fwd_fc1post[BLOCK_SIZE][MLP_DIM];
static double fwd_x3 [BLOCK_SIZE][N_EMBD];
static double fwd_logits [BLOCK_SIZE][VOCAB_SIZE];
static double fwd_probs [BLOCK_SIZE][VOCAB_SIZE];
/* ── Forward helpers ─────────────────────────────────────────────── */
static double do_rmsnorm(const double *x, double *y, int D) {
double ms = 0;
for (int i = 0; i < D; i++) ms += x[i] * x[i];
ms /= D;
double scale = 1.0 / sqrt(ms + 1e-5);
for (int i = 0; i < D; i++) y[i] = x[i] * scale;
return scale;
}
static void do_linear(const double *x, int off, int nout, int nin, double *y) {
for (int r = 0; r < nout; r++) {
double s = 0;
for (int c = 0; c < nin; c++)
s += params[off + r * nin + c] * x[c];
y[r] = s;
}
}
static void do_softmax(const double *in, double *out, int n) {
double mx = in[0];
for (int i = 1; i < n; i++) if (in[i] > mx) mx = in[i];
double total = 0;
for (int i = 0; i < n; i++) { out[i] = exp(in[i] - mx); total += out[i]; }
for (int i = 0; i < n; i++) out[i] /= total;
}
/* ── Training forward pass ───────────────────────────────────────── */
static double train_forward(const int *tokens, int n) {
for (int t = 0; t < n; t++) {
int tok = tokens[t];
int pos = t;
for (int i = 0; i < N_EMBD; i++)
fwd_emb[t][i] = W(OFF_WTE, tok, i, N_EMBD) + W(OFF_WPE, pos, i, N_EMBD);
fwd_x0_scale[t] = do_rmsnorm(fwd_emb[t], fwd_x0[t], N_EMBD);
memcpy(fwd_xr1[t], fwd_x0[t], N_EMBD * sizeof(double));
fwd_xn1_sc[t] = do_rmsnorm(fwd_x0[t], fwd_xn1[t], N_EMBD);
do_linear(fwd_xn1[t], OFF_WQ, N_EMBD, N_EMBD, fwd_q[t]);
do_linear(fwd_xn1[t], OFF_WK, N_EMBD, N_EMBD, fwd_k[t]);
do_linear(fwd_xn1[t], OFF_WV, N_EMBD, N_EMBD, fwd_v[t]);
double inv_sqrt_hd = 1.0 / sqrt((double)HEAD_DIM);
for (int h = 0; h < N_HEAD; h++) {
int hs = h * HEAD_DIM;
double attn_logits[BLOCK_SIZE];
for (int s = 0; s <= t; s++) {
double dot = 0;
for (int j = 0; j < HEAD_DIM; j++)
dot += fwd_q[t][hs+j] * fwd_k[s][hs+j];
attn_logits[s] = dot * inv_sqrt_hd;
}
double mx = attn_logits[0];
for (int s = 1; s <= t; s++) if (attn_logits[s] > mx) mx = attn_logits[s];
double total = 0;
for (int s = 0; s <= t; s++) {
fwd_attn_w[t][h][s] = exp(attn_logits[s] - mx);
total += fwd_attn_w[t][h][s];
}
for (int s = 0; s <= t; s++) fwd_attn_w[t][h][s] /= total;
for (int j = 0; j < HEAD_DIM; j++) {
double acc = 0;
for (int s = 0; s <= t; s++)
acc += fwd_attn_w[t][h][s] * fwd_v[s][hs+j];
fwd_xa[t][hs+j] = acc;
}
}
double wo_out[N_EMBD];
do_linear(fwd_xa[t], OFF_WO, N_EMBD, N_EMBD, wo_out);
for (int i = 0; i < N_EMBD; i++)
fwd_x2[t][i] = wo_out[i] + fwd_xr1[t][i];
fwd_xn2_sc[t] = do_rmsnorm(fwd_x2[t], fwd_xn2[t], N_EMBD);
do_linear(fwd_xn2[t], OFF_FC1, MLP_DIM, N_EMBD, fwd_fc1pre[t]);
for (int i = 0; i < MLP_DIM; i++)
fwd_fc1post[t][i] = fwd_fc1pre[t][i] > 0 ? fwd_fc1pre[t][i] : 0;
double fc2_out[N_EMBD];
do_linear(fwd_fc1post[t], OFF_FC2, N_EMBD, MLP_DIM, fc2_out);
for (int i = 0; i < N_EMBD; i++)
fwd_x3[t][i] = fc2_out[i] + fwd_x2[t][i];
do_linear(fwd_x3[t], OFF_LMHEAD, VOCAB_SIZE, N_EMBD, fwd_logits[t]);
do_softmax(fwd_logits[t], fwd_probs[t], VOCAB_SIZE);
}
double loss = 0;
for (int t = 0; t < n; t++) {
int target = tokens[t + 1];
loss += -log(fwd_probs[t][target]);
}
return loss / n;
}
/* ── Backward pass (two-phase) ───────────────────────────────────── */
static double bwd_dk[BLOCK_SIZE][N_EMBD];
static double bwd_dv[BLOCK_SIZE][N_EMBD];
static double bwd_d_xr1[BLOCK_SIZE][N_EMBD];
static void train_backward(const int *tokens, int n) {
memset(grads, 0, sizeof(grads));
memset(bwd_dk, 0, sizeof(bwd_dk));
memset(bwd_dv, 0, sizeof(bwd_dv));
double d_q[BLOCK_SIZE][N_EMBD];
/* Phase 1 (reverse): loss → lm_head → MLP → attention */
for (int t = n - 1; t >= 0; t--) {
int target = tokens[t + 1];
double d_logits[VOCAB_SIZE];
for (int i = 0; i < VOCAB_SIZE; i++)
d_logits[i] = (fwd_probs[t][i] - (i == target ? 1.0 : 0.0)) / n;
double d_x3[N_EMBD];
memset(d_x3, 0, sizeof(d_x3));
for (int r = 0; r < VOCAB_SIZE; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_LMHEAD, r, c, N_EMBD) += d_logits[r] * fwd_x3[t][c];
d_x3[c] += d_logits[r] * params[OFF_LMHEAD + r * N_EMBD + c];
}
}
double d_x2[N_EMBD];
memcpy(d_x2, d_x3, sizeof(d_x2));
double d_fc1post[MLP_DIM];
memset(d_fc1post, 0, sizeof(d_fc1post));
for (int r = 0; r < N_EMBD; r++) {
for (int c = 0; c < MLP_DIM; c++) {
G(OFF_FC2, r, c, MLP_DIM) += d_x3[r] * fwd_fc1post[t][c];
d_fc1post[c] += d_x3[r] * params[OFF_FC2 + r * MLP_DIM + c];
}
}
double d_fc1pre[MLP_DIM];
for (int i = 0; i < MLP_DIM; i++)
d_fc1pre[i] = fwd_fc1pre[t][i] > 0 ? d_fc1post[i] : 0;
double d_xn2[N_EMBD];
memset(d_xn2, 0, sizeof(d_xn2));
for (int r = 0; r < MLP_DIM; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_FC1, r, c, N_EMBD) += d_fc1pre[r] * fwd_xn2[t][c];
d_xn2[c] += d_fc1pre[r] * params[OFF_FC1 + r * N_EMBD + c];
}
}
{
double sc = fwd_xn2_sc[t];
double dot = 0;
for (int i = 0; i < N_EMBD; i++) dot += d_xn2[i] * fwd_x2[t][i];
double sc3 = sc * sc * sc;
double coeff = sc3 * dot / N_EMBD;
for (int i = 0; i < N_EMBD; i++)
d_x2[i] += sc * d_xn2[i] - fwd_x2[t][i] * coeff;
}
memcpy(bwd_d_xr1[t], d_x2, N_EMBD * sizeof(double));
double d_xa[N_EMBD];
memset(d_xa, 0, sizeof(d_xa));
for (int r = 0; r < N_EMBD; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_WO, r, c, N_EMBD) += d_x2[r] * fwd_xa[t][c];
d_xa[c] += d_x2[r] * params[OFF_WO + r * N_EMBD + c];
}
}
double inv_sqrt_hd = 1.0 / sqrt((double)HEAD_DIM);
memset(d_q[t], 0, sizeof(d_q[t]));
for (int h = 0; h < N_HEAD; h++) {
int hs = h * HEAD_DIM;
double d_attn_w[BLOCK_SIZE];
memset(d_attn_w, 0, sizeof(d_attn_w));
for (int s = 0; s <= t; s++) {
for (int j = 0; j < HEAD_DIM; j++) {
d_attn_w[s] += d_xa[hs+j] * fwd_v[s][hs+j];
bwd_dv[s][hs+j] += d_xa[hs+j] * fwd_attn_w[t][h][s];
}
}
double dot_aw = 0;
for (int s = 0; s <= t; s++)
dot_aw += fwd_attn_w[t][h][s] * d_attn_w[s];
double d_attn_logits[BLOCK_SIZE];
for (int s = 0; s <= t; s++)
d_attn_logits[s] = fwd_attn_w[t][h][s] * (d_attn_w[s] - dot_aw);
for (int s = 0; s <= t; s++) {
for (int j = 0; j < HEAD_DIM; j++) {
d_q[t][hs+j] += d_attn_logits[s] * fwd_k[s][hs+j] * inv_sqrt_hd;
bwd_dk[s][hs+j] += d_attn_logits[s] * fwd_q[t][hs+j] * inv_sqrt_hd;
}
}
}
}
/* Phase 2 (forward): QKV projections → rmsnorm → embeddings */
for (int t = 0; t < n; t++) {
int tok = tokens[t];
int pos = t;
double d_xn1_t[N_EMBD];
memset(d_xn1_t, 0, sizeof(d_xn1_t));
for (int r = 0; r < N_EMBD; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_WQ, r, c, N_EMBD) += d_q[t][r] * fwd_xn1[t][c];
d_xn1_t[c] += d_q[t][r] * params[OFF_WQ + r * N_EMBD + c];
}
}
for (int r = 0; r < N_EMBD; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_WK, r, c, N_EMBD) += bwd_dk[t][r] * fwd_xn1[t][c];
d_xn1_t[c] += bwd_dk[t][r] * params[OFF_WK + r * N_EMBD + c];
}
}
for (int r = 0; r < N_EMBD; r++) {
for (int c = 0; c < N_EMBD; c++) {
G(OFF_WV, r, c, N_EMBD) += bwd_dv[t][r] * fwd_xn1[t][c];
d_xn1_t[c] += bwd_dv[t][r] * params[OFF_WV + r * N_EMBD + c];
}
}
double d_x0_t[N_EMBD];
{
double sc = fwd_xn1_sc[t];
double dot = 0;
for (int i = 0; i < N_EMBD; i++) dot += d_xn1_t[i] * fwd_x0[t][i];
double sc3 = sc * sc * sc;
double coeff = sc3 * dot / N_EMBD;
for (int i = 0; i < N_EMBD; i++)
d_x0_t[i] = sc * d_xn1_t[i] - fwd_x0[t][i] * coeff;
}
for (int i = 0; i < N_EMBD; i++)
d_x0_t[i] += bwd_d_xr1[t][i];
double d_emb_t[N_EMBD];
{
double sc = fwd_x0_scale[t];
double dot = 0;
for (int i = 0; i < N_EMBD; i++) dot += d_x0_t[i] * fwd_emb[t][i];
double sc3 = sc * sc * sc;
double coeff = sc3 * dot / N_EMBD;
for (int i = 0; i < N_EMBD; i++)
d_emb_t[i] = sc * d_x0_t[i] - fwd_emb[t][i] * coeff;
}
for (int i = 0; i < N_EMBD; i++) {
G(OFF_WTE, tok, i, N_EMBD) += d_emb_t[i];
G(OFF_WPE, pos, i, N_EMBD) += d_emb_t[i];
}
}
}
/* ── Inference forward ───────────────────────────────────────────── */
static double inf_k[BLOCK_SIZE][N_EMBD];
static double inf_v[BLOCK_SIZE][N_EMBD];
static void inference_forward(int tok, int pos, double *logits_out) {
double x[N_EMBD], tmp[N_EMBD];
for (int i = 0; i < N_EMBD; i++)
x[i] = W(OFF_WTE, tok, i, N_EMBD) + W(OFF_WPE, pos, i, N_EMBD);
do_rmsnorm(x, x, N_EMBD);
double xr[N_EMBD];
memcpy(xr, x, sizeof(xr));
do_rmsnorm(x, tmp, N_EMBD);
double q[N_EMBD], k[N_EMBD], v[N_EMBD];
do_linear(tmp, OFF_WQ, N_EMBD, N_EMBD, q);
do_linear(tmp, OFF_WK, N_EMBD, N_EMBD, k);
do_linear(tmp, OFF_WV, N_EMBD, N_EMBD, v);
memcpy(inf_k[pos], k, sizeof(k));
memcpy(inf_v[pos], v, sizeof(v));
double xa[N_EMBD];
double inv_sqrt_hd = 1.0 / sqrt((double)HEAD_DIM);
for (int h = 0; h < N_HEAD; h++) {
int hs = h * HEAD_DIM;
double attn_logits[BLOCK_SIZE];
for (int s = 0; s <= pos; s++) {
double dot = 0;
for (int j = 0; j < HEAD_DIM; j++)
dot += q[hs+j] * inf_k[s][hs+j];
attn_logits[s] = dot * inv_sqrt_hd;
}
double mx = attn_logits[0];
for (int s = 1; s <= pos; s++) if (attn_logits[s] > mx) mx = attn_logits[s];
double weights[BLOCK_SIZE], total = 0;
for (int s = 0; s <= pos; s++) {
weights[s] = exp(attn_logits[s] - mx);
total += weights[s];
}
for (int s = 0; s <= pos; s++) weights[s] /= total;
for (int j = 0; j < HEAD_DIM; j++) {
double acc = 0;
for (int s = 0; s <= pos; s++)
acc += weights[s] * inf_v[s][hs+j];
xa[hs+j] = acc;
}
}
double wo_out[N_EMBD];
do_linear(xa, OFF_WO, N_EMBD, N_EMBD, wo_out);
for (int i = 0; i < N_EMBD; i++)
x[i] = wo_out[i] + xr[i];
memcpy(xr, x, sizeof(xr));
do_rmsnorm(x, tmp, N_EMBD);
double h1[MLP_DIM];
do_linear(tmp, OFF_FC1, MLP_DIM, N_EMBD, h1);
for (int i = 0; i < MLP_DIM; i++) if (h1[i] < 0) h1[i] = 0;
double fc2[N_EMBD];
do_linear(h1, OFF_FC2, N_EMBD, MLP_DIM, fc2);
for (int i = 0; i < N_EMBD; i++)
x[i] = fc2[i] + xr[i];
do_linear(x, OFF_LMHEAD, VOCAB_SIZE, N_EMBD, logits_out);
}
/* ── Main ────────────────────────────────────────────────────────── */
int main(void) {
uint32_t seed_key[1] = {42};
mt_init_by_array(seed_key, 1);
load_data();
rng_shuffle(doc_idx, num_docs);
printf("num docs: %d\n", num_docs);
printf("vocab size: %d\n", VOCAB_SIZE);
for (int i = 0; i < NUM_PARAMS; i++)
params[i] = rng_gauss(0, 0.08);
printf("num params: %d\n", NUM_PARAMS);
memset(adam_m, 0, sizeof(adam_m));
memset(adam_v, 0, sizeof(adam_v));
double lr = 0.01, beta1 = 0.85, beta2 = 0.99, eps_adam = 1e-8;
int num_steps = 1000;
for (int step = 0; step < num_steps; step++) {
const char *doc = docs[doc_idx[step % num_docs]];
int doclen = (int)strlen(doc);
int tokens[BLOCK_SIZE + 2];
int copylen = doclen;
if (copylen > BLOCK_SIZE - 1) copylen = BLOCK_SIZE - 1;
tokens[0] = VOCAB_SIZE - 1;
for (int i = 0; i < copylen; i++)
tokens[i + 1] = doc[i] - 'a';
tokens[copylen + 1] = VOCAB_SIZE - 1;
int n = copylen + 1;
double loss = train_forward(tokens, n);
train_backward(tokens, n);
double lr_t = lr * (1.0 - (double)step / num_steps);
double bc1 = 1.0 - pow(beta1, step + 1);
double bc2 = 1.0 - pow(beta2, step + 1);
for (int i = 0; i < NUM_PARAMS; i++) {
adam_m[i] = beta1 * adam_m[i] + (1.0 - beta1) * grads[i];
adam_v[i] = beta2 * adam_v[i] + (1.0 - beta2) * grads[i] * grads[i];
double m_hat = adam_m[i] / bc1;
double v_hat = adam_v[i] / bc2;
params[i] -= lr_t * m_hat / (sqrt(v_hat) + eps_adam);
}
printf("step %4d / %4d | loss %.4f\n", step + 1, num_steps, loss);
}
double temperature = 0.5;
printf("--- inference (new, hallucinated names) ---\n");
for (int s = 0; s < 20; s++) {
int tok = VOCAB_SIZE - 1;
char sample[BLOCK_SIZE + 1];
int slen = 0;
for (int pos = 0; pos < BLOCK_SIZE; pos++) {
double logits[VOCAB_SIZE];
inference_forward(tok, pos, logits);
for (int i = 0; i < VOCAB_SIZE; i++) logits[i] /= temperature;
double probs[VOCAB_SIZE];
do_softmax(logits, probs, VOCAB_SIZE);
tok = rng_weighted_choice(probs, VOCAB_SIZE);
if (tok == VOCAB_SIZE - 1) break;
sample[slen++] = 'a' + tok;
}
sample[slen] = '\0';
printf("sample %2d: %s\n", s + 1, sample);
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment