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%
9.7 KB · 211 lines
Raw Blame History
1// Author: Simon-Pierre Boucher — contact@spboucher.ai2//3// Row-wise RMSNorm / LayerNorm. One threadgroup per row; simd reductions4// first (register shuffles beat threadgroup memory 2:1 on Apple GPUs),5// tiny threadgroup scratch only to cross simdgroups. LayerNorm is two-pass6// (mean, then variance) — the E[x²]−E[x]² shortcut loses precision and7// these kernels are held to 1e-4 parity vs the two-pass CPU reference.8#include <metal_stdlib>9using namespace metal;1011namespace {1213// Threadgroup-wide sum of one per-thread value; returns the same total to14// every thread. Scratch must hold 32 floats.15inline float tg_sum(float v, uint lane, uint simd_idx, uint n_simds,16                    threadgroup float* scratch) {17    const float s = simd_sum(v);18    if (simd_is_first()) scratch[simd_idx] = s;19    threadgroup_barrier(mem_flags::mem_threadgroup);20    const float mine = (lane < n_simds) ? scratch[lane] : 0.0f;21    return simd_sum(mine);22}2324} // namespace2526struct NormParams {27    uint C;28    float eps;29};3031kernel void rmsnorm_f32(device const float* X   [[buffer(0)]],32                        device const float* W   [[buffer(1)]],33                        device float*       OUT [[buffer(2)]],34                        constant NormParams& p  [[buffer(3)]],35                        uint row      [[threadgroup_position_in_grid]],36                        uint lid      [[thread_index_in_threadgroup]],37                        uint tg_size  [[threads_per_threadgroup]],38                        uint lane     [[thread_index_in_simdgroup]],39                        uint simd_idx [[simdgroup_index_in_threadgroup]],40                        uint n_simds  [[simdgroups_per_threadgroup]]) {41    device const float* x = X + ulong(row) * p.C;42    device float* out = OUT + ulong(row) * p.C;43    threadgroup float scratch[32];4445    float ss = 0.0f;46    for (uint j = lid; j < p.C; j += tg_size) ss = fma(x[j], x[j], ss);47    ss = tg_sum(ss, lane, simd_idx, n_simds, scratch);4849    const float inv_rms = rsqrt(ss / float(p.C) + p.eps);50    for (uint j = lid; j < p.C; j += tg_size) out[j] = W[j] * x[j] * inv_rms;51}5253kernel void layernorm_f32(device const float* X   [[buffer(0)]],54                          device const float* W   [[buffer(1)]],55                          device const float* B   [[buffer(2)]],56                          device float*       OUT [[buffer(3)]],57                          constant NormParams& p  [[buffer(4)]],58                          uint row      [[threadgroup_position_in_grid]],59                          uint lid      [[thread_index_in_threadgroup]],60                          uint tg_size  [[threads_per_threadgroup]],61                          uint lane     [[thread_index_in_simdgroup]],62                          uint simd_idx [[simdgroup_index_in_threadgroup]],63                          uint n_simds  [[simdgroups_per_threadgroup]]) {64    device const float* x = X + ulong(row) * p.C;65    device float* out = OUT + ulong(row) * p.C;66    threadgroup float scratch[32];6768    float s = 0.0f;69    for (uint j = lid; j < p.C; j += tg_size) s += x[j];70    const float mean = tg_sum(s, lane, simd_idx, n_simds, scratch) / float(p.C);71    threadgroup_barrier(mem_flags::mem_threadgroup); // scratch reuse7273    float var = 0.0f;74    for (uint j = lid; j < p.C; j += tg_size) {75        const float d = x[j] - mean;76        var = fma(d, d, var);77    }78    var = tg_sum(var, lane, simd_idx, n_simds, scratch) / float(p.C);7980    const float inv_std = rsqrt(var + p.eps);81    for (uint j = lid; j < p.C; j += tg_size)82        out[j] = fma(W[j], (x[j] - mean) * inv_std, B[j]);83}8485// ---- backwards -------------------------------------------------------------86// dx is row-parallel (one threadgroup per row) and saves the row statistics87// (inv_rms, or mean+inv_std) it computes anyway; dw/db are column-parallel88// (one thread per column) reading those saved stats — deterministic, no89// atomics, no per-column recomputation.9091kernel void rmsnorm_bwd_dx_f32(device const float* X       [[buffer(0)]],92                               device const float* W       [[buffer(1)]],93                               device const float* dOUT    [[buffer(2)]],94                               device float*       dX      [[buffer(3)]],95                               device float*       inv_rms [[buffer(4)]], // [rows]96                               constant NormParams& p      [[buffer(5)]],97                               uint row      [[threadgroup_position_in_grid]],98                               uint lid      [[thread_index_in_threadgroup]],99                               uint tg_size  [[threads_per_threadgroup]],100                               uint lane     [[thread_index_in_simdgroup]],101                               uint simd_idx [[simdgroup_index_in_threadgroup]],102                               uint n_simds  [[simdgroups_per_threadgroup]]) {103    device const float* x = X + ulong(row) * p.C;104    device const float* d = dOUT + ulong(row) * p.C;105    device float* dx = dX + ulong(row) * p.C;106    threadgroup float scratch[32];107108    float ss = 0.0f;109    for (uint j = lid; j < p.C; j += tg_size) ss = fma(x[j], x[j], ss);110    ss = tg_sum(ss, lane, simd_idx, n_simds, scratch);111    const float ir = rsqrt(ss / float(p.C) + p.eps);112    if (lid == 0) inv_rms[row] = ir;113    threadgroup_barrier(mem_flags::mem_threadgroup);114115    float dot = 0.0f; // sum_j g_j w_j x_j116    for (uint j = lid; j < p.C; j += tg_size) dot = fma(d[j] * W[j], x[j], dot);117    dot = tg_sum(dot, lane, simd_idx, n_simds, scratch);118119    const float coef = dot * ir * ir * ir / float(p.C);120    for (uint j = lid; j < p.C; j += tg_size)121        dx[j] += d[j] * W[j] * ir - x[j] * coef;122}123124// dw[j] += sum_i g[i,j] * x[i,j] * inv_rms[i]125kernel void rmsnorm_bwd_dw_f32(device const float* X       [[buffer(0)]],126                               device const float* dOUT    [[buffer(1)]],127                               device const float* inv_rms [[buffer(2)]],128                               device float*       dW      [[buffer(3)]],129                               constant NormParams& p      [[buffer(4)]],130                               constant uint&      rows    [[buffer(5)]],131                               uint j [[thread_position_in_grid]]) {132    float acc = 0.0f;133    for (uint i = 0; i < rows; ++i)134        acc = fma(dOUT[ulong(i) * p.C + j] * X[ulong(i) * p.C + j], inv_rms[i], acc);135    dW[j] += acc;136}137138kernel void layernorm_bwd_dx_f32(device const float* X    [[buffer(0)]],139                                 device const float* W    [[buffer(1)]],140                                 device const float* dOUT [[buffer(2)]],141                                 device float*       dX   [[buffer(3)]],142                                 device float*       mean_out [[buffer(4)]], // [rows]143                                 device float*       istd_out [[buffer(5)]], // [rows]144                                 constant NormParams& p   [[buffer(6)]],145                                 uint row      [[threadgroup_position_in_grid]],146                                 uint lid      [[thread_index_in_threadgroup]],147                                 uint tg_size  [[threads_per_threadgroup]],148                                 uint lane     [[thread_index_in_simdgroup]],149                                 uint simd_idx [[simdgroup_index_in_threadgroup]],150                                 uint n_simds  [[simdgroups_per_threadgroup]]) {151    device const float* x = X + ulong(row) * p.C;152    device const float* d = dOUT + ulong(row) * p.C;153    device float* dx = dX + ulong(row) * p.C;154    threadgroup float scratch[32];155156    float s = 0.0f;157    for (uint j = lid; j < p.C; j += tg_size) s += x[j];158    const float mean = tg_sum(s, lane, simd_idx, n_simds, scratch) / float(p.C);159    threadgroup_barrier(mem_flags::mem_threadgroup);160161    float var = 0.0f;162    for (uint j = lid; j < p.C; j += tg_size) {163        const float dv = x[j] - mean;164        var = fma(dv, dv, var);165    }166    var = tg_sum(var, lane, simd_idx, n_simds, scratch) / float(p.C);167    const float inv_std = rsqrt(var + p.eps);168    if (lid == 0) {169        mean_out[row] = mean;170        istd_out[row] = inv_std;171    }172    threadgroup_barrier(mem_flags::mem_threadgroup);173174    float m_dxhat = 0.0f, m_dxhat_xhat = 0.0f;175    for (uint j = lid; j < p.C; j += tg_size) {176        const float xhat = (x[j] - mean) * inv_std;177        const float dxhat = d[j] * W[j];178        m_dxhat += dxhat;179        m_dxhat_xhat = fma(dxhat, xhat, m_dxhat_xhat);180    }181    m_dxhat = tg_sum(m_dxhat, lane, simd_idx, n_simds, scratch) / float(p.C);182    threadgroup_barrier(mem_flags::mem_threadgroup);183    m_dxhat_xhat = tg_sum(m_dxhat_xhat, lane, simd_idx, n_simds, scratch) / float(p.C);184185    for (uint j = lid; j < p.C; j += tg_size) {186        const float xhat = (x[j] - mean) * inv_std;187        dx[j] += inv_std * (d[j] * W[j] - m_dxhat - xhat * m_dxhat_xhat);188    }189}190191// dw[j] += sum_i g[i,j] * xhat[i,j] ; db[j] += sum_i g[i,j]192kernel void layernorm_bwd_dwdb_f32(device const float* X    [[buffer(0)]],193                                   device const float* dOUT [[buffer(1)]],194                                   device const float* mean [[buffer(2)]],195                                   device const float* istd [[buffer(3)]],196                                   device float*       dW   [[buffer(4)]],197                                   device float*       dB   [[buffer(5)]],198                                   constant NormParams& p   [[buffer(6)]],199                                   constant uint&      rows [[buffer(7)]],200                                   uint j [[thread_position_in_grid]]) {201    float aw = 0.0f, ab = 0.0f;202    for (uint i = 0; i < rows; ++i) {203        const float g = dOUT[ulong(i) * p.C + j];204        const float xhat = (X[ulong(i) * p.C + j] - mean[i]) * istd[i];205        aw = fma(g, xhat, aw);206        ab += g;207    }208    dW[j] += aw;209    dB[j] += ab;210}211