c-llm/doc/layernorm/layernorm.c
2024-04-09 20:14:18 +02:00

175 lines
6.1 KiB
C

// must run `python layernorm.py` first to generate the reference data
// then compile for example as `gcc layernorm.c -o layernorm -lm`
// and then run as `./layernorm` to see the output
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
void layernorm_forward(float* out, float* mean, float* rstd,
float* inp, float* weight, float* bias,
int B, int T, int C) {
float eps = 1e-5f;
for (int b = 0; b < B; b++) {
for (int t = 0; t < T; t++) {
// seek to the input position inp[b,t,:]
float* x = inp + b * T * C + t * C;
// calculate the mean
float m = 0.0f;
for (int i = 0; i < C; i++) {
m += x[i];
}
m = m/C;
// calculate the variance (without any bias correction)
float v = 0.0f;
for (int i = 0; i < C; i++) {
float xshift = x[i] - m;
v += xshift * xshift;
}
v = v/C;
// calculate the rstd
float s = 1.0f / sqrtf(v + eps);
// seek to the output position in out[b,t,:]
float* out_bt = out + b * T * C + t * C;
for (int i = 0; i < C; i++) {
float n = (s * (x[i] - m)); // normalized output
float o = n * weight[i] + bias[i]; // scale and shift it
out_bt[i] = o; // write
}
// cache the mean and rstd for the backward pass later
mean[b * T + t] = m;
rstd[b * T + t] = s;
}
}
}
void layernorm_backward(float* dinp, float* dweight, float* dbias,
float* dout, float* inp, float* weight, float* mean, float* rstd,
int B, int T, int C) {
for (int b = 0; b < B; b++) {
for (int t = 0; t < T; t++) {
float* dout_bt = dout + b * T * C + t * C;
float* inp_bt = inp + b * T * C + t * C;
float* dinp_bt = dinp + b * T * C + t * C;
float mean_bt = mean[b * T + t];
float rstd_bt = rstd[b * T + t];
// first: two reduce operations
float dnorm_mean = 0.0f;
float dnorm_norm_mean = 0.0f;
for (int i = 0; i < C; i++) {
float norm_bti = (inp_bt[i] - mean_bt) * rstd_bt;
float dnorm_i = weight[i] * dout_bt[i];
dnorm_mean += dnorm_i;
dnorm_norm_mean += dnorm_i * norm_bti;
}
dnorm_mean = dnorm_mean / C;
dnorm_norm_mean = dnorm_norm_mean / C;
// now iterate again and accumulate all the gradients
for (int i = 0; i < C; i++) {
float norm_bti = (inp_bt[i] - mean_bt) * rstd_bt;
float dnorm_i = weight[i] * dout_bt[i];
// gradient contribution to bias
dbias[i] += dout_bt[i];
// gradient contribution to weight
dweight[i] += norm_bti * dout_bt[i];
// gradient contribution to input
float dval = 0.0f;
dval += dnorm_i; // term 1
dval -= dnorm_mean; // term 2
dval -= norm_bti * dnorm_norm_mean; // term 3
dval *= rstd_bt; // final scale
dinp_bt[i] += dval;
}
}
}
}
// poor man's tensor checker
int check_tensor(float *a, float *b, int n, char* label) {
int ok = 1;
printf("%s\n", label);
for (int i = 0; i < n; i++) {
if (fabs(a[i] - b[i]) <= 1e-5) {
printf("OK ");
} else {
printf("NOT OK ");
ok = 0;
}
printf("%f %f\n", a[i], b[i]);
}
return ok;
}
int main() {
int B = 2; // batch
int T = 3; // time / sequence length
int C = 4; // number of channels
float* x = (float*) malloc(B * T * C * sizeof(float));
float* w = (float*) malloc(C * sizeof(float));
float* b = (float*) malloc(C * sizeof(float));
float* out = (float*) malloc(B * T * C * sizeof(float));
float* mean = (float*) malloc(B * T * sizeof(float));
float* rstd = (float*) malloc(B * T * sizeof(float));
float* dout = (float*) malloc(B * T * C * sizeof(float));
float* dx = (float*) malloc(B * T * C * sizeof(float));
float* dw = (float*) malloc(C * sizeof(float));
float* db = (float*) malloc(C * sizeof(float));
// read reference information from Python
FILE *file = fopen("ln.bin", "rb");
if (file == NULL) {
printf("Error opening file\n");
return 1;
}
fread(x, sizeof(float), B * T * C, file);
fread(w, sizeof(float), C, file);
fread(b, sizeof(float), C, file);
fread(out, sizeof(float), B * T * C, file);
fread(mean, sizeof(float), B * T, file);
fread(rstd, sizeof(float), B * T, file);
fread(dout, sizeof(float), B * T * C, file);
fread(dx, sizeof(float), B * T * C, file);
fread(dw, sizeof(float), C, file);
fread(db, sizeof(float), C, file);
fclose(file);
// now let's calculate everything ourselves
// forward pass
float* c_out = (float*) malloc(B * T * C * sizeof(float));
float* c_mean = (float*) malloc(B * T * sizeof(float));
float* c_rstd = (float*) malloc(B * T * sizeof(float));
layernorm_forward(c_out, c_mean, c_rstd, x, w, b, B, T, C);
// check correctness of forward pass
check_tensor(out, c_out, B*T*C, "out");
check_tensor(mean, c_mean, B*T, "mean");
check_tensor(rstd, c_rstd, B*T, "rstd");
// backward pass (note calloc inits grads to zero)
float* c_dx = (float*) calloc(B * T * C, sizeof(float));
float* c_dw = (float*) calloc(B * T, sizeof(float));
float* c_db = (float*) calloc(B * T, sizeof(float));
layernorm_backward(c_dx, c_dw, c_db, dout, x, w, c_mean, c_rstd, B, T, C);
// check correctness of backward pass
check_tensor(c_dx, dx, B*T*C, "dx");
check_tensor(c_dw, dw, C, "dw");
check_tensor(c_db, db, C, "db");
free(x);
free(w);
free(b);
free(out);
free(mean);
free(rstd);
free(dout);
free(dx);
free(dw);
free(db);
return 0;
}