SPB Git

spb/forge Public MIT

Forge — LLM training from scratch in pure C++20 + Metal on Apple Silicon.

C++ 61.2% C 23% Python 7.6% TeX 7.2% CMake 1.1%
7.9 KB · 192 lines
Raw Blame History
1// Author: Simon-Pierre Boucher — contact@spboucher.ai2//3// Elementwise kernels. Flat 1-D dispatch via dispatchThreads (non-uniform4// threadgroups — no bounds-check tail), threadgroup size a multiple of 32.5// Purely bandwidth-bound; float4-vectorized variants come with the M56// fusion pass.7#include <metal_stdlib>8using namespace metal;910kernel void add_f32(device const float* a   [[buffer(0)]],11                    device const float* b   [[buffer(1)]],12                    device float*       out [[buffer(2)]],13                    uint gid [[thread_position_in_grid]]) {14    out[gid] = a[gid] + b[gid];15}1617kernel void mul_f32(device const float* a   [[buffer(0)]],18                    device const float* b   [[buffer(1)]],19                    device float*       out [[buffer(2)]],20                    uint gid [[thread_position_in_grid]]) {21    out[gid] = a[gid] * b[gid];22}2324kernel void scale_f32(device const float* a   [[buffer(0)]],25                      device float*       out [[buffer(1)]],26                      constant float&     s   [[buffer(2)]],27                      uint gid [[thread_position_in_grid]]) {28    out[gid] = a[gid] * s;29}3031// out[i] = x[i] + bias[i % C] — row-broadcast bias32kernel void add_bias_f32(device const float* x    [[buffer(0)]],33                         device const float* bias [[buffer(1)]],34                         device float*       out  [[buffer(2)]],35                         constant uint&      C    [[buffer(3)]],36                         uint gid [[thread_position_in_grid]]) {37    out[gid] = x[gid] + bias[gid % C];38}3940kernel void silu_f32(device const float* x   [[buffer(0)]],41                     device float*       out [[buffer(1)]],42                     uint gid [[thread_position_in_grid]]) {43    const float v = x[gid];44    out[gid] = v / (1.0f + exp(-v));45}4647kernel void gelu_f32(device const float* x   [[buffer(0)]],48                     device float*       out [[buffer(1)]],49                     uint gid [[thread_position_in_grid]]) {50    const float v = x[gid];51    const float k = 0.7978845608028654f; // sqrt(2/pi)52    out[gid] = 0.5f * v * (1.0f + precise::tanh(k * (v + 0.044715f * v * v * v)));53}5455kernel void sigmoid_f32(device const float* x   [[buffer(0)]],56                        device float*       out [[buffer(1)]],57                        uint gid [[thread_position_in_grid]]) {58    out[gid] = 1.0f / (1.0f + exp(-x[gid]));59}6061// dx += dout * y * (1 - y), using the forward output y.62kernel void sigmoid_bwd_f32(device const float* y    [[buffer(0)]],63                            device const float* dout [[buffer(1)]],64                            device float*       dx   [[buffer(2)]],65                            uint gid [[thread_position_in_grid]]) {66    const float v = y[gid];67    dx[gid] = fma(dout[gid], v * (1.0f - v), dx[gid]);68}6970// ReLU² (nanoGPT-speedrun lineage): out = max(x, 0)^2.71kernel void relu2_f32(device const float* x   [[buffer(0)]],72                      device float*       out [[buffer(1)]],73                      uint gid [[thread_position_in_grid]]) {74    const float v = max(x[gid], 0.0f);75    out[gid] = v * v;76}7778kernel void relu2_bwd_f32(device const float* x    [[buffer(0)]],79                          device const float* dout [[buffer(1)]],80                          device float*       dx   [[buffer(2)]],81                          uint gid [[thread_position_in_grid]]) {82    dx[gid] = fma(dout[gid], 2.0f * max(x[gid], 0.0f), dx[gid]);83}8485// Gemma-style logit softcap: out = cap * tanh(x / cap).86kernel void softcap_f32(device const float* x   [[buffer(0)]],87                        device float*       out [[buffer(1)]],88                        constant float&     cap [[buffer(2)]],89                        uint gid [[thread_position_in_grid]]) {90    out[gid] = cap * precise::tanh(x[gid] / cap);91}9293// dx += dout * (1 - (y/cap)^2), using the forward output y.94kernel void softcap_bwd_f32(device const float* y    [[buffer(0)]],95                            device const float* dout [[buffer(1)]],96                            device float*       dx   [[buffer(2)]],97                            constant float&     cap  [[buffer(3)]],98                            uint gid [[thread_position_in_grid]]) {99    const float t = y[gid] / cap;100    dx[gid] = fma(dout[gid], 1.0f - t * t, dx[gid]);101}102103// ---- backward / accumulation kernels (all ACCUMULATE into their outputs) ----104105kernel void accum_f32(device float*       dst [[buffer(0)]],106                      device const float* src [[buffer(1)]],107                      uint gid [[thread_position_in_grid]]) {108    dst[gid] += src[gid];109}110111// dst += src * s ; s in a 1-element buffer so it can be produced on-GPU112// (e.g. d(loss) scaling) without a sync.113kernel void axpy_f32(device float*       dst [[buffer(0)]],114                     device const float* src [[buffer(1)]],115                     device const float* s   [[buffer(2)]],116                     uint gid [[thread_position_in_grid]]) {117    dst[gid] = fma(src[gid], s[0], dst[gid]);118}119120kernel void silu_bwd_f32(device const float* x    [[buffer(0)]],121                         device const float* dout [[buffer(1)]],122                         device float*       dx   [[buffer(2)]],123                         uint gid [[thread_position_in_grid]]) {124    const float v = x[gid];125    const float sig = 1.0f / (1.0f + exp(-v));126    dx[gid] = fma(dout[gid], sig * (1.0f + v * (1.0f - sig)), dx[gid]);127}128129kernel void gelu_bwd_f32(device const float* x    [[buffer(0)]],130                         device const float* dout [[buffer(1)]],131                         device float*       dx   [[buffer(2)]],132                         uint gid [[thread_position_in_grid]]) {133    const float v = x[gid];134    const float k = 0.7978845608028654f;135    const float u = k * (v + 0.044715f * v * v * v);136    const float t = precise::tanh(u);137    const float du = k * (1.0f + 3.0f * 0.044715f * v * v);138    dx[gid] = fma(dout[gid], 0.5f * (1.0f + t) + 0.5f * v * (1.0f - t * t) * du, dx[gid]);139}140141// dbias[j] += sum_rows dout[i,j] — one thread per column, strided rows.142// Column-major walk is uncoalesced but this kernel is a tiny fraction of a143// step; revisit in the M5 fusion pass if it ever shows in a trace.144kernel void add_bias_bwd_f32(device const float* dout  [[buffer(0)]],145                             device float*       dbias [[buffer(1)]],146                             constant uint2&     nc    [[buffer(2)]], // (N, C)147                             uint j [[thread_position_in_grid]]) {148    float acc = 0.0f;149    for (uint i = 0; i < nc.x; ++i) acc += dout[ulong(i) * nc.y + j];150    dbias[j] += acc;151}152153// ---- RoPE (interleaved pairs; INV=true applies the inverse rotation and154// accumulates — the backward pass) ----155156constant bool ROPE_INV [[function_constant(2)]];157158struct RopeParams {159    uint T, H, HD;160    uint pos_offset;161};162163// freqs[k] is the per-pair inverse frequency (theta^(-2k/HD), possibly164// rescaled — llama3 rope_scaling, per-layer theta). Host-precomputed.165kernel void rope_f32(device const float* x     [[buffer(0)]],166                     device float*       out   [[buffer(1)]],167                     constant RopeParams& p    [[buffer(2)]],168                     device const float* freqs [[buffer(3)]],169                     uint gid [[thread_position_in_grid]]) {170    // gid indexes (bt, h, k) pairs: one thread per rotated pair171    const uint pairs_per_row = p.H * (p.HD / 2);172    const uint bt = gid / pairs_per_row;173    const uint rem = gid % pairs_per_row;174    const uint h = rem / (p.HD / 2);175    const uint k = rem % (p.HD / 2);176177    const float pos = float(bt % p.T + p.pos_offset);178    const float angle = pos * freqs[k];179    const float c = cos(angle);180    const float s = ROPE_INV ? -sin(angle) : sin(angle);181182    const ulong i0 = ulong(bt) * (p.H * p.HD) + h * p.HD + 2 * k;183    const float x0 = x[i0], x1 = x[i0 + 1];184    if (ROPE_INV) {185        out[i0]     += x0 * c - x1 * s;186        out[i0 + 1] += x0 * s + x1 * c;187    } else {188        out[i0]     = x0 * c - x1 * s;189        out[i0 + 1] = x0 * s + x1 * c;190    }191}192