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%

History of src/nn/attention.h · clear filter

  1. Add architecture-variant waves 1+2: train Mistral/Qwen/Gemma/OLMo-class models by config
    Wave 1 (module-level):
    - rope refactor: kernels read a host-precomputed inv-freq table; unlocks
      HF-"llama3" rope scaling (rope_scale_*), per-layer theta, and NoPE
      layers (nope_every, SmolLM3)
    - attention_bias (Qwen2.5 QKV bias), head_dim decoupled from
      d_model/n_heads (Qwen3), relu2 activation (nanoGPT-speedrun lineage),
      norm_placement pre|post|sandwich (OLMo2/Gemma)
    
    Wave 2 (attention kernels):
    - sliding_window + sliding_global_every (Mistral / Gemma3 local:global
      patterns) in the CPU reference, the unfused Metal kernels, and the
      fused scalar flash kernels — out-of-window KV blocks are skipped, so
      cost scales with the window; window > 0 auto-routes off the MMA kernel
    - attn_softcap (Gemma2): cap*tanh on scores pre-softmax, unfused path,
      exact tanh' chain in all backwards
    - rope_theta_global for dual-theta local/global layers (Gemma3)
    
    Parity suites cover every knob (fused + unfused paths); gradcheck and
    overfit stay green. New demo configs: gpt-50m-mistral, gpt-50m-gemma;
    ARCHITECTURES.md documents the per-family config matrix.
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    simon-pierre boucher committed 5 days ago (Aug 5, 2026) · 1 file changed +39 −19
  2. Add QAT, MoE, and architecture-variant knobs — all config-selected
    - quant "int8"|"ternary": per-row fake-quant each forward (BitNet-style
      absmean for ternary), straight-through estimator backward, f32 masters;
      wired through the Linear quantization seam
    - n_experts/moe_top_k/n_shared_experts: softmax router, renormalized top-k
      gates (topk_renorm + row_scale ops, CPU+Metal), differentiable
      load-balance loss, DeepSeek-style always-active shared experts;
      v1 computes experts densely (correctness first)
    - qk_norm (Qwen3/Gemma3), final_softcap (Gemma2), scale_embeddings (Gemma)
    - new kernels: quant.metal, moe.metal, softcap in elementwise.metal
    - CPU references + parity tests for every new op and full-model variants
      (QAT int8/ternary, MoE 4+1shared, qk-norm+softcap+embed-scale)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
    simon-pierre boucher committed 5 days ago (Aug 5, 2026) · 1 file changed +26 −1
  3. Forge: LLM training from scratch in C++20 + Metal on Apple Silicon
    A complete transformer training stack with no ML dependencies: tensors,
    autograd, hand-written Metal kernels, flash attention (forward and backward),
    AdamW, BPE tokenizer, checkpointing and generation. Architecture is fully
    config-driven — the same binary trains 12M to 205M parameter models.
    
    Every Metal kernel is validated against a CPU reference (85 parity checks,
    <=1e-4, most bit-exact), gradients against central finite differences, and
    each optimization was accepted only after the training loss trajectory stayed
    numerically unchanged.
    
    Measured findings (M5 Max, documented in RESEARCH.md and paper/forge.tex):
    
    - `constant constexpr` for MSL tile constants declares an address-space
      variable, not a compile-time constant. Loops stop unrolling and every
      matrix accumulator spills: 0.82 -> 10.21 TFLOPS once switched to enums.
    - That defect is invisible in the AIR at every -O level, because unrolling
      happens in the driver back end. Benchmark; do not read the IR.
    - Register pressure, not bandwidth, dominates attention backward. Guided by
      measured spill counts, three restructurings took it 107 -> 7.05 ms (15.2x).
    - On M5, mpp::tensor_ops::matmul2d reaches 51.5 TFLOPS with f16 operands vs
      10.6 for a tuned simdgroup_matrix kernel (4.9x), verified numerically.
      f16 on the simdgroup path alone is worth only +18-22%.
    - Concurrent dispatch for the optimizer sweep: +22% on the 100M config.
    
    Trained the 12.2M config for one epoch over 19.14M TinyStories tokens:
    loss 8.40 -> 2.99, validation 3.009, perplexity 20.27, ~38.2k tokens/sec.
    Simon-Pierre Boucher committed 10 days ago (Jul 31, 2026) · 1 file changed +71