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%
1# Author: Simon-Pierre Boucher — contact@spboucher.ai2cmake_minimum_required(VERSION 3.24)3project(forge LANGUAGES CXX)45set(CMAKE_CXX_STANDARD 20)6set(CMAKE_CXX_STANDARD_REQUIRED ON)7set(CMAKE_CXX_EXTENSIONS OFF)89if(NOT CMAKE_BUILD_TYPE)10 set(CMAKE_BUILD_TYPE Release)11endif()1213add_compile_options(-Wall -Wextra)1415# ---------------------------------------------------------------------------16# Metal shader library: .metal -> .air -> forge.metallib17# Compiled with -std=metal3.2 (macOS 15+ baseline: simdgroup_matrix, bfloat).18# Debug builds embed sources for the Xcode GPU debugger.19# ---------------------------------------------------------------------------20file(GLOB METAL_SOURCES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/src/kernels/*.metal)21set(AIR_FILES "")22foreach(shader ${METAL_SOURCES})23 get_filename_component(shader_name ${shader} NAME_WE)24 set(air ${CMAKE_BINARY_DIR}/${shader_name}.air)25 # Metal Performance Primitives (cooperative tensors) need the Metal 426 # language; everything else targets 3.2 so it runs on macOS 15+.27 if(shader_name MATCHES "_mpp$")28 set(metal_std "-std=metal4.0")29 else()30 set(metal_std "-std=metal3.2")31 endif()32 add_custom_command(33 OUTPUT ${air}34 # -frecord-sources is unconditional, not Debug-only: without shader sources35 # embedded in the metallib, the Metal debugger and gpudebug report36 # "no source" and every per-shader/per-line profiling view comes back37 # empty. It costs file size, nothing else.38 COMMAND xcrun -sdk macosx metal ${metal_std} -O239 -gline-tables-only -frecord-sources40 -c ${shader} -o ${air}41 DEPENDS ${shader}42 COMMAND_EXPAND_LISTS43 COMMENT "Compiling Metal shader ${shader_name}.metal"44 VERBATIM)45 list(APPEND AIR_FILES ${air})46endforeach()4748add_custom_command(49 OUTPUT ${CMAKE_BINARY_DIR}/forge.metallib50 # Link with `metal`, not `metallib`: the sources only survive into the51 # library if -frecord-sources is passed at link time too, and `metallib`52 # rejects that flag ("unknown argument").53 COMMAND xcrun -sdk macosx metal -frecord-sources ${AIR_FILES}54 -o ${CMAKE_BINARY_DIR}/forge.metallib55 DEPENDS ${AIR_FILES}56 COMMENT "Linking forge.metallib"57 VERBATIM)58add_custom_target(forge_kernels ALL DEPENDS ${CMAKE_BINARY_DIR}/forge.metallib)5960# ---------------------------------------------------------------------------61# Core library62# ---------------------------------------------------------------------------63add_library(forge_core STATIC64 src/core/metal_impl.cpp65 src/core/tensor.cpp66 src/core/allocator.cpp67 src/core/device.cpp68 src/core/autograd.cpp69 src/core/fmodel.cpp70 src/ops/cpu/cpu_ops.cpp71 src/ops/metal/metal_ops.cpp72 src/ops/ops.cpp73 src/nn/linear.cpp74 src/train/optimizer.cpp75 src/train/dataloader.cpp76 src/train/checkpoint.cpp77 src/train/trainer.cpp78 src/tokenizer/bpe.cpp)7980target_include_directories(forge_core PUBLIC81 ${CMAKE_SOURCE_DIR}/src82 ${CMAKE_SOURCE_DIR}/third_party83 ${CMAKE_SOURCE_DIR}/third_party/metal-cpp)8485target_link_libraries(forge_core PUBLIC86 "-framework Metal" "-framework Foundation" "-framework QuartzCore")8788add_dependencies(forge_core forge_kernels)8990# ---------------------------------------------------------------------------91# CLI92# ---------------------------------------------------------------------------93add_executable(forge src/main.cpp)94target_link_libraries(forge PRIVATE forge_core)9596# ---------------------------------------------------------------------------97# Tests98# ---------------------------------------------------------------------------99enable_testing()100foreach(t test_ops test_gradcheck test_overfit test_tokenizer)101 add_executable(${t} tests/${t}.cpp)102 target_link_libraries(${t} PRIVATE forge_core)103 add_test(NAME ${t} COMMAND ${t} WORKING_DIRECTORY ${CMAKE_BINARY_DIR})104endforeach()105106# Benchmarks: built, not run by ctest.107foreach(b bench_matmul bench_attention bench_precision mppcheck)108 add_executable(${b} tests/${b}.cpp)109 target_link_libraries(${b} PRIVATE forge_core)110endforeach()111