diff --git a/training/stories_cpu_ops.h b/training/stories_cpu_ops.h index c9f2cfa..dfe35f9 100644 --- a/training/stories_cpu_ops.h +++ b/training/stories_cpu_ops.h @@ -1,129 +1,130 @@ -// stories_cpu_ops.h — CPU operations: RMSNorm, cross-entropy, Adam, softmax -#pragma once -#include "stories_config.h" - -static float *g_rms_tmp = NULL; - -static void rmsnorm(float *out, const float *x, const float *w, int d, int S) { - if (!g_rms_tmp) g_rms_tmp = (float*)malloc(S*4); - float *ss = (float*)calloc(S, sizeof(float)); - for (int i=0; in; i++) { - s->m[i] = b1*s->m[i] + (1-b1)*g[i]; - s->v[i] = b2*s->v[i] + (1-b2)*g[i]*g[i]; - float mh = s->m[i]/bc1, vh = s->v[i]/bc2; - w[i] -= lr * mh / (sqrtf(vh) + eps); - } -} - -// Cross-entropy loss + gradient for logits (column-major: [VOCAB, SEQ]) -// logits[v*SEQ+t] = logit for vocab v, position t -// targets[t] = target token id for position t -// Returns mean CE loss, writes dlogits = softmax(logits) - one_hot(targets) -// Data is column-major [V, S], but we process per-column (stride=1 within col is v*S+t, stride between v's is S) -// For vDSP: transpose to row-major scratch [S, V] to vectorize softmax per position -static float cross_entropy_loss(float *dlogits, const float *logits, const uint16_t *targets, int V, int S) { - // Work in transposed layout [S, V] where each row is one position's logits (contiguous) - float *buf = (float*)malloc(S * V * 4); - // Transpose [V,S] → [S,V]: buf[t*V+v] = logits[v*S+t] - vDSP_mtrans(logits, 1, buf, 1, (vDSP_Length)S, (vDSP_Length)V); - - float total_loss = 0; - float invS = 1.0f / S; - for (int t = 0; t < S; t++) { - float *row = buf + t * V; - // max - float maxv; - vDSP_maxv(row, 1, &maxv, (vDSP_Length)V); - // row -= maxv - float neg_max = -maxv; - vDSP_vsadd(row, 1, &neg_max, row, 1, (vDSP_Length)V); - // exp in-place - int n = V; - vvexpf(row, row, &n); - // sum - float sum; - vDSP_sve(row, 1, &sum, (vDSP_Length)V); - // normalize - float inv_sum = 1.0f / sum; - vDSP_vsmul(row, 1, &inv_sum, row, 1, (vDSP_Length)V); - // loss - int tgt = targets[t]; - total_loss -= logf(row[tgt] + 1e-10f); - // gradient: softmax - one_hot, then /S - row[tgt] -= 1.0f; - vDSP_vsmul(row, 1, &invS, row, 1, (vDSP_Length)V); - } - // Transpose back [S,V] → [V,S] - vDSP_mtrans(buf, 1, dlogits, 1, (vDSP_Length)V, (vDSP_Length)S); - free(buf); - return total_loss / S; -} - -// Embedding lookup: token_ids → x [DIM, SEQ] (channel-first) -// embed is [VOCAB, DIM] row-major (vocab_size rows, dim cols) -static void embed_lookup(float *x, const float *embed, const uint16_t *tokens, int dim, int seq) { - for (int t = 0; t < seq; t++) { - int tok = tokens[t]; - for (int d = 0; d < dim; d++) { - x[d*seq + t] = embed[tok*dim + d]; - } - } -} - -// Embedding backward: accumulate dE[tok] += dx[:,t] for each position -static void embed_backward(float *d_embed, const float *dx, const uint16_t *tokens, int dim, int seq) { - for (int t = 0; t < seq; t++) { - int tok = tokens[t]; - for (int d = 0; d < dim; d++) { - d_embed[tok*dim + d] += dx[d*seq + t]; - } - } -} +// stories_cpu_ops.h — CPU operations: RMSNorm, cross-entropy, Adam, softmax +#pragma once +#include "stories_config.h" + +static float *g_rms_tmp = NULL; + +static void rmsnorm(float *out, const float *x, const float *w, int d, int S) { + if (!g_rms_tmp) g_rms_tmp = (float*)malloc(S*4); + float *ss = (float*)calloc(S, sizeof(float)); + for (int i=0; in; i++) { + s->m[i] = b1*s->m[i] + (1-b1)*g[i]; + s->v[i] = b2*s->v[i] + (1-b2)*g[i]*g[i]; + float mh = s->m[i]/bc1, vh = s->v[i]/bc2; + w[i] -= lr * mh / (sqrtf(vh) + eps); + } +} + +// Cross-entropy loss + gradient for logits (column-major: [VOCAB, SEQ]) +// logits[v*SEQ+t] = logit for vocab v, position t +// targets[t] = target token id for position t +// Returns mean CE loss, writes dlogits = softmax(logits) - one_hot(targets) +// Data is column-major [V, S], but we process per-column (stride=1 within col is v*S+t, stride between v's is S) +// For vDSP: transpose to row-major scratch [S, V] to vectorize softmax per position +static float cross_entropy_loss(float *dlogits, const float *logits, const uint16_t *targets, int V, int S) { + // Work in transposed layout [S, V] where each row is one position's logits (contiguous) + float *buf = (float*)malloc(S * V * 4); + // Transpose [V,S] → [S,V]: buf[t*V+v] = logits[v*S+t] + vDSP_mtrans(logits, 1, buf, 1, (vDSP_Length)S, (vDSP_Length)V); + + float total_loss = 0; + float invS = 1.0f / S; + for (int t = 0; t < S; t++) { + float *row = buf + t * V; + // max + float maxv; + vDSP_maxv(row, 1, &maxv, (vDSP_Length)V); + // row -= maxv + float neg_max = -maxv; + vDSP_vsadd(row, 1, &neg_max, row, 1, (vDSP_Length)V); + // exp in-place + int n = V; + vvexpf(row, row, &n); + // sum + float sum; + vDSP_sve(row, 1, &sum, (vDSP_Length)V); + // normalize + float inv_sum = 1.0f / sum; + vDSP_vsmul(row, 1, &inv_sum, row, 1, (vDSP_Length)V); + // loss + int tgt = targets[t]; + total_loss -= logf(row[tgt] + 1e-10f); + // gradient: softmax - one_hot, then /S + row[tgt] -= 1.0f; + vDSP_vsmul(row, 1, &invS, row, 1, (vDSP_Length)V); + } + // Transpose back [S,V] → [V,S] + vDSP_mtrans(buf, 1, dlogits, 1, (vDSP_Length)V, (vDSP_Length)S); + free(buf); + return total_loss / S; +} + +// Embedding lookup: token_ids → x [DIM, SEQ] (channel-first) +// embed is [VOCAB, DIM] row-major (vocab_size rows, dim cols) +static void embed_lookup(float *x, const float *embed, const uint16_t *tokens, int dim, int seq) { + for (int t = 0; t < seq; t++) { + int tok = tokens[t]; + if (tok >= VOCAB) { tok = 0; } // HIGH-01: clamp invalid token -> position 0 + for (int d = 0; d < dim; d++) { + x[d*seq + t] = embed[tok*dim + d]; + } + } +} + +// Embedding backward: accumulate dE[tok] += dx[:,t] for each position +static void embed_backward(float *d_embed, const float *dx, const uint16_t *tokens, int dim, int seq) { + for (int t = 0; t < seq; t++) { + int tok = tokens[t]; + for (int d = 0; d < dim; d++) { + d_embed[tok*dim + d] += dx[d*seq + t]; + } + } +} diff --git a/training/train_large.m b/training/train_large.m index 0a70a32..785204d 100644 --- a/training/train_large.m +++ b/training/train_large.m @@ -1,705 +1,709 @@ -// train_large.m — Train stories110M (12 layers, 768dim, 3072hidden) on ANE -// Uses pretokenized TinyStories data with cross-entropy loss -// 5 weight-bearing ANE kernels per layer × 12 layers = 60 per compile batch -#include "stories_io.h" -#include "stories_mil.h" -#include "stories_cpu_ops.h" - -#define CKPT_PATH "ane_stories110M_ckpt.bin" -#define MODEL_PATH "../../assets/models/stories110M.bin" -#define DATA_PATH "tinystories_data00.bin" - -// ===== Weight loading from llama2.c format ===== -static bool load_pretrained(LayerWeights *lw, float *rms_final, float *embed, const char *path) { - FILE *f = fopen(path, "rb"); - if (!f) { printf("Cannot open %s\n", path); return false; } - Llama2Config cfg; - // Validate config read — gatekeeper before any dimension-based logic (CRIT-03) - if (fread(&cfg, sizeof(cfg), 1, f) != 1) { - printf(" ERROR: Config read failed (truncated file?)\n"); - fclose(f); return false; - } - printf(" Model config: dim=%d hidden=%d layers=%d heads=%d vocab=%d seq=%d\n", - cfg.dim, cfg.hidden_dim, cfg.n_layers, cfg.n_heads, abs(cfg.vocab_size), cfg.seq_len); - if (cfg.dim != DIM || cfg.hidden_dim != HIDDEN || cfg.n_layers != NLAYERS) { - printf(" ERROR: Config mismatch! Expected dim=%d hidden=%d layers=%d\n", DIM, HIDDEN, NLAYERS); - fclose(f); return false; - } - int V = abs(cfg.vocab_size); - bool shared = cfg.vocab_size > 0; - - // Read in llama2.c order: embed, rms_att[all], wq[all], wk[all], wv[all], wo[all], - // rms_ffn[all], w1[all], w2[all], w3[all], rms_final, [wcls] - fread(embed, 4, V * DIM, f); - - // rms_att weights for all layers (contiguous) - for (int L = 0; L < NLAYERS; L++) fread(lw[L].rms_att, 4, DIM, f); - // wq for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].Wq, 4, WQ_SZ, f); - // wk for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].Wk, 4, WQ_SZ, f); - // wv for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].Wv, 4, WQ_SZ, f); - // wo for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].Wo, 4, WO_SZ, f); - // rms_ffn weights for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].rms_ffn, 4, DIM, f); - // w1 for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].W1, 4, W1_SZ, f); - // w2 for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].W2, 4, W2_SZ, f); - // w3 for all layers - for (int L = 0; L < NLAYERS; L++) fread(lw[L].W3, 4, W3_SZ, f); - // rms_final - fread(rms_final, 4, DIM, f); - // wcls = embed if shared (we just use embed pointer) - - fclose(f); - printf(" Loaded pretrained weights (%s)\n", shared ? "shared embed/cls" : "separate cls"); - return true; -} - -// ===== Compile one layer's kernels ===== -static bool compile_layer_kernels(LayerKernels *lk, LayerWeights *w) { - lk->fwdAttn = compile_kern_mil_w(gen_sdpa_fwd_taps(), (@{ - @"@model_path/weights/rms1.bin": @{@"offset":@0, @"data":build_blob(w->rms_att,1,DIM)}, - @"@model_path/weights/wq.bin": @{@"offset":@0, @"data":build_blob(w->Wq,DIM,DIM)}, - @"@model_path/weights/wk.bin": @{@"offset":@0, @"data":build_blob(w->Wk,DIM,DIM)}, - @"@model_path/weights/wv.bin": @{@"offset":@0, @"data":build_blob(w->Wv,DIM,DIM)}, - @"@model_path/weights/wo.bin": @{@"offset":@0, @"data":build_blob(w->Wo,DIM,DIM)}, - @"@model_path/weights/mask.bin": @{@"offset":@0, @"data":get_mask_blob()}, - }), DIM*SEQ*2, 6*DIM*SEQ*2); - - lk->fwdFFN = compile_kern_mil_w(gen_ffn_fwd_taps(), (@{ - @"@model_path/weights/rms2.bin": @{@"offset":@0, @"data":build_blob(w->rms_ffn,1,DIM)}, - @"@model_path/weights/w1.bin": @{@"offset":@0, @"data":build_blob(w->W1,HIDDEN,DIM)}, - @"@model_path/weights/w3.bin": @{@"offset":@0, @"data":build_blob(w->W3,HIDDEN,DIM)}, - @"@model_path/weights/w2.bin": @{@"offset":@0, @"data":build_blob(w->W2,DIM,HIDDEN)}, - }), DIM*SEQ*2, (2*DIM+3*HIDDEN)*SEQ*2); - - lk->ffnBwd = compile_kern_mil_w(gen_ffn_bwd(), (@{ - @"@model_path/weights/w2t.bin": @{@"offset":@0, @"data":build_blob_t(w->W2,DIM,HIDDEN)}, - @"@model_path/weights/w1t.bin": @{@"offset":@0, @"data":build_blob_t(w->W1,HIDDEN,DIM)}, - @"@model_path/weights/w3t.bin": @{@"offset":@0, @"data":build_blob_t(w->W3,HIDDEN,DIM)}, - }), (DIM+2*HIDDEN)*SEQ*2, (DIM+2*HIDDEN)*SEQ*2); - - lk->sdpaBwd1 = compile_kern_mil_w(gen_sdpa_bwd1(), (@{ - @"@model_path/weights/mask.bin": @{@"offset":@0, @"data":get_mask_blob()}, - @"@model_path/weights/wot.bin": @{@"offset":@0, @"data":build_blob_t(w->Wo,DIM,DIM)}, - }), 4*DIM*SEQ*2, (DIM+2*SCORE_CH)*SEQ*2); - - lk->qkvBwd = compile_kern_mil_w(gen_qkvb(), (@{ - @"@model_path/weights/wqt.bin": @{@"offset":@0, @"data":build_blob_t(w->Wq,DIM,DIM)}, - @"@model_path/weights/wkt.bin": @{@"offset":@0, @"data":build_blob_t(w->Wk,DIM,DIM)}, - @"@model_path/weights/wvt.bin": @{@"offset":@0, @"data":build_blob_t(w->Wv,DIM,DIM)}, - }), 3*DIM*SEQ*2, DIM*SEQ*2); - - return lk->fwdAttn && lk->fwdFFN && lk->ffnBwd && lk->sdpaBwd1 && lk->qkvBwd; -} - -// Compile weight-free sdpaBwd2 (only needs once, no weights) -static Kern *compile_sdpa_bwd2(void) { - return compile_kern_mil_w(gen_sdpa_bwd2(), @{}, - (2*SCORE_CH+2*DIM)*SEQ*2, 2*DIM*SEQ*2); -} - -static void free_layer_kernels(LayerKernels *lk) { - free_kern(lk->fwdAttn); free_kern(lk->fwdFFN); free_kern(lk->ffnBwd); - free_kern(lk->sdpaBwd1); free_kern(lk->qkvBwd); - // sdpaBwd2 is shared, freed separately - lk->fwdAttn = lk->fwdFFN = lk->ffnBwd = lk->sdpaBwd1 = lk->qkvBwd = NULL; -} - -// ===== Checkpoint save/load ===== -static void save_checkpoint(const char *path, int step, int total_steps, float lr, float loss, - double cc, double ct, double cw, int cs, int cb, int adam_t, - LayerWeights *lw, LayerAdam *la, float *rms_final, AdamState *arms_final, - float *embed, AdamState *aembed) { - FILE *f = fopen(path, "wb"); - if (!f) { fprintf(stderr, "save_checkpoint: cannot open %s\n", path); return; } // CRIT-03 - CkptHdr h = {0}; - h.magic = 0x424C5A54; h.version = 2; - h.step = step; h.total_steps = total_steps; - h.n_layers = NLAYERS; h.vocab_size = VOCAB; h.dim = DIM; - h.hidden_dim = HIDDEN; h.n_heads = HEADS; h.seq_len = SEQ; - h.lr = lr; h.loss = loss; - h.cum_compile = cc; h.cum_train = ct; h.cum_wall = cw; - h.cum_steps = cs; h.cum_batches = cb; h.adam_t = adam_t; - h.pad[0] = 0x01020304; // byte-order sentinel (MED-04): LE marker, see CkptHdr - fwrite(&h, sizeof(h), 1, f); - // Per-layer weights + adam - for (int L = 0; L < NLAYERS; L++) { - fwrite(lw[L].Wq,4,WQ_SZ,f); fwrite(lw[L].Wk,4,WQ_SZ,f); - fwrite(lw[L].Wv,4,WQ_SZ,f); fwrite(lw[L].Wo,4,WO_SZ,f); - fwrite(lw[L].W1,4,W1_SZ,f); fwrite(lw[L].W2,4,W2_SZ,f); fwrite(lw[L].W3,4,W3_SZ,f); - fwrite(lw[L].rms_att,4,DIM,f); fwrite(lw[L].rms_ffn,4,DIM,f); - // Adam state - fwrite(la[L].Wq.m,4,WQ_SZ,f); fwrite(la[L].Wq.v,4,WQ_SZ,f); - fwrite(la[L].Wk.m,4,WQ_SZ,f); fwrite(la[L].Wk.v,4,WQ_SZ,f); - fwrite(la[L].Wv.m,4,WQ_SZ,f); fwrite(la[L].Wv.v,4,WQ_SZ,f); - fwrite(la[L].Wo.m,4,WO_SZ,f); fwrite(la[L].Wo.v,4,WO_SZ,f); - fwrite(la[L].W1.m,4,W1_SZ,f); fwrite(la[L].W1.v,4,W1_SZ,f); - fwrite(la[L].W2.m,4,W2_SZ,f); fwrite(la[L].W2.v,4,W2_SZ,f); - fwrite(la[L].W3.m,4,W3_SZ,f); fwrite(la[L].W3.v,4,W3_SZ,f); - fwrite(la[L].rms_att.m,4,DIM,f); fwrite(la[L].rms_att.v,4,DIM,f); - fwrite(la[L].rms_ffn.m,4,DIM,f); fwrite(la[L].rms_ffn.v,4,DIM,f); - } - fwrite(rms_final,4,DIM,f); - fwrite(arms_final->m,4,DIM,f); fwrite(arms_final->v,4,DIM,f); - fwrite(embed,4,VOCAB*DIM,f); - fwrite(aembed->m,4,VOCAB*DIM,f); fwrite(aembed->v,4,VOCAB*DIM,f); - fclose(f); -} - -static bool load_checkpoint(const char *path, int *step, int *total_steps, float *lr, float *loss, - double *cc, double *ct, double *cw, int *cs, int *cb, int *adam_t, - LayerWeights *lw, LayerAdam *la, float *rms_final, AdamState *arms_final, - float *embed, AdamState *aembed) { - FILE *f = fopen(path, "rb"); - if (!f) return false; - CkptHdr h; - // Validate header read before magic-byte check (CRIT-03) - if (fread(&h, sizeof(h), 1, f) != 1) { - fprintf(stderr, "load_checkpoint: header read failed\n"); - fclose(f); return false; - } - if (h.magic != 0x424C5A54 || h.version != 2) { fclose(f); return false; } - // MED-04: Byte-order check. pad[0]=0 = legacy checkpoint (no sentinel, accept). - // pad[0]=0x01020304 = LE ok. Anything else = big-endian or corrupt checkpoint. - _Static_assert(__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__, - "Checkpoint format is little-endian (Apple Silicon only)"); - if (h.pad[0] != 0 && h.pad[0] != 0x01020304) { - fprintf(stderr, "load_checkpoint: byte-order mismatch (big-endian checkpoint?)\n"); - fclose(f); return false; - } - *step = h.step; *total_steps = h.total_steps; *lr = h.lr; *loss = h.loss; - *cc = h.cum_compile; *ct = h.cum_train; *cw = h.cum_wall; - *cs = h.cum_steps; *cb = h.cum_batches; *adam_t = h.adam_t; - for (int L = 0; L < NLAYERS; L++) { - fread(lw[L].Wq,4,WQ_SZ,f); fread(lw[L].Wk,4,WQ_SZ,f); - fread(lw[L].Wv,4,WQ_SZ,f); fread(lw[L].Wo,4,WO_SZ,f); - fread(lw[L].W1,4,W1_SZ,f); fread(lw[L].W2,4,W2_SZ,f); fread(lw[L].W3,4,W3_SZ,f); - fread(lw[L].rms_att,4,DIM,f); fread(lw[L].rms_ffn,4,DIM,f); - fread(la[L].Wq.m,4,WQ_SZ,f); fread(la[L].Wq.v,4,WQ_SZ,f); - fread(la[L].Wk.m,4,WQ_SZ,f); fread(la[L].Wk.v,4,WQ_SZ,f); - fread(la[L].Wv.m,4,WQ_SZ,f); fread(la[L].Wv.v,4,WQ_SZ,f); - fread(la[L].Wo.m,4,WO_SZ,f); fread(la[L].Wo.v,4,WO_SZ,f); - fread(la[L].W1.m,4,W1_SZ,f); fread(la[L].W1.v,4,W1_SZ,f); - fread(la[L].W2.m,4,W2_SZ,f); fread(la[L].W2.v,4,W2_SZ,f); - fread(la[L].W3.m,4,W3_SZ,f); fread(la[L].W3.v,4,W3_SZ,f); - fread(la[L].rms_att.m,4,DIM,f); fread(la[L].rms_att.v,4,DIM,f); - fread(la[L].rms_ffn.m,4,DIM,f); fread(la[L].rms_ffn.v,4,DIM,f); - } - fread(rms_final,4,DIM,f); - fread(arms_final->m,4,DIM,f); fread(arms_final->v,4,DIM,f); - fread(embed,4,VOCAB*DIM,f); - fread(aembed->m,4,VOCAB*DIM,f); fread(aembed->v,4,VOCAB*DIM,f); - fclose(f); - return true; -} - -// ===== Main ===== -int main(int argc, char *argv[]) { - @autoreleasepool { - setbuf(stdout, NULL); - ane_init(); - mach_timebase_info(&g_tb); - - int total_steps = 10000; - float lr = 3e-4f; - float adam_b1=0.9f, adam_b2=0.999f, adam_eps=1e-8f; - int adam_t = 0, start_step = 0; - - // Parse args - bool do_resume = false; - for (int i=1; i