CC ?= clang CFLAGS = -Ofast -Wno-unused-result -Wno-ignored-pragmas -Wno-unknown-attributes LDFLAGS = LDLIBS = -lm INCLUDES = CFLAGS_COND = -march=native # Find nvcc SHELL_UNAME = $(shell uname) REMOVE_FILES = rm -f OUTPUT_FILE = -o $@ CUDA_OUTPUT_FILE = -o $@ # NVCC flags # -t=0 is short for --threads, 0 = number of CPUs on the machine NVCC_FLAGS = -O3 -t=0 --use_fast_math NVCC_LDFLAGS = -lcublas -lcublasLt NVCC_INCLUDES = NVCC_LDLIBS = NCLL_INCUDES = # overridable flag for multi-GPU training. by default we won't build with cudnn # because it bloats up the compile time from a few seconds to ~minute USE_CUDNN ?= 0 # autodect a lot of various supports on current platform $(info ---------------------------------------------) ifneq ($(OS), Windows_NT) NVCC := $(shell which nvcc 2>/dev/null) # Function to test if the compiler accepts a given flag. define check_and_add_flag $(eval FLAG_SUPPORTED := $(shell printf "int main() { return 0; }\n" | $(CC) $(1) -x c - -o /dev/null 2>/dev/null && echo 'yes')) ifeq ($(FLAG_SUPPORTED),yes) CFLAGS += $(1) endif endef # Check each flag and add it if supported $(foreach flag,$(CFLAGS_COND),$(eval $(call check_and_add_flag,$(flag)))) else CFLAGS := REMOVE_FILES = del *.exe,*.obj,*.lib,*.exp,*.pdb && del SHELL_UNAME := Windows ifneq ($(shell where nvcc 2> nul),"") NVCC := nvcc else NVCC := endif CC := cl CFLAGS = /Idev /Zi /nologo /Wall /WX- /diagnostics:column /sdl /O2 /Oi /Ot /GL /D _DEBUG /D _CONSOLE /D _UNICODE /D UNICODE /Gm- /EHsc /MD /GS /Gy /fp:fast /Zc:wchar_t /Zc:forScope /Zc:inline /permissive- \ /external:W3 /Gd /TP /wd4996 /Fd$@.pdb /FC /openmp:llvm LDFLAGS := LDLIBS := INCLUDES := NVCC_FLAGS += -I"dev" ifeq ($(WIN_CI_BUILD),1) $(info Windows CI build) OUTPUT_FILE = /link /OUT:$@ CUDA_OUTPUT_FILE = -o $@ else $(info Windows local build) OUTPUT_FILE = /link /OUT:$@ && copy /Y $@ $@.exe CUDA_OUTPUT_FILE = -o $@ && copy /Y $@.exe $@ endif endif # Check and include cudnn if available # Currently hard-coding a bunch of stuff here for Linux, todo make this better/nicer # You need cuDNN from: https://developer.nvidia.com/cudnn # Follow the apt-get instructions # And the cuDNN front-end from: https://github.com/NVIDIA/cudnn-frontend/tree/main # For this there is no installation, just download the repo to your home directory # and then we include it below (see currently hard-coded path assumed in home directory) ifeq ($(USE_CUDNN), 1) ifeq ($(SHELL_UNAME), Linux) # hard-coded path for now CUDNN_FRONTEND_PATH := $(HOME)/cudnn-frontend/include ifeq ($(shell [ -d $(CUDNN_FRONTEND_PATH) ] && echo "exists"), exists) $(info ✓ cuDNN found, will run with flash-attention) NVCC_INCLUDES += -I$(CUDNN_FRONTEND_PATH) NVCC_LDFLAGS += -lcudnn NVCC_FLAGS += -DENABLE_CUDNN else $(error ✗ cuDNN not found. See the Makefile for our currently hard-coded paths / install instructions) endif else $(info → cuDNN is not supported right now outside of Linux) endif else $(info → cuDNN is manually disabled by default, run make with `USE_CUDNN=1` to try to enable) endif # Check if OpenMP is available # This is done by attempting to compile an empty file with OpenMP flags # OpenMP makes the code a lot faster so I advise installing it # e.g. on MacOS: brew install libomp # e.g. on Ubuntu: sudo apt-get install libomp-dev # later, run the program by prepending the number of threads, e.g.: OMP_NUM_THREADS=8 ./gpt2 # First, check if NO_OMP is set to 1, if not, proceed with the OpenMP checks ifeq ($(NO_OMP), 1) $(info OpenMP is manually disabled) else ifneq ($(OS), Windows_NT) # Detect if running on macOS or Linux ifeq ($(SHELL_UNAME), Darwin) # Check for Homebrew's libomp installation in different common directories ifeq ($(shell [ -d /opt/homebrew/opt/libomp/lib ] && echo "exists"), exists) # macOS with Homebrew on ARM (Apple Silicon) CFLAGS += -Xclang -fopenmp -DOMP LDFLAGS += -L/opt/homebrew/opt/libomp/lib LDLIBS += -lomp INCLUDES += -I/opt/homebrew/opt/libomp/include $(info ✓ OpenMP found) else ifeq ($(shell [ -d /usr/local/opt/libomp/lib ] && echo "exists"), exists) # macOS with Homebrew on Intel CFLAGS += -Xclang -fopenmp -DOMP LDFLAGS += -L/usr/local/opt/libomp/lib LDLIBS += -lomp INCLUDES += -I/usr/local/opt/libomp/include $(info ✓ OpenMP found) else $(info ✗ OpenMP not found) endif else # Check for OpenMP support in GCC or Clang on Linux ifeq ($(shell echo | $(CC) -fopenmp -x c -E - > /dev/null 2>&1; echo $$?), 0) CFLAGS += -fopenmp -DOMP LDLIBS += -lgomp $(info ✓ OpenMP found) else $(info ✗ OpenMP not found) endif endif endif endif # Check if OpenMPI and NCCL are available, include them if so, for multi-GPU training ifeq ($(NO_MULTI_GPU), 1) $(info → Multi-GPU (OpenMPI + NCCL) is manually disabled) else ifneq ($(OS), Windows_NT) # Detect if running on macOS or Linux ifeq ($(SHELL_UNAME), Darwin) $(info ✗ Multi-GPU on CUDA on Darwin is not supported, skipping OpenMPI + NCCL support) else ifeq ($(shell [ -d /usr/lib/x86_64-linux-gnu/openmpi/lib/ ] && [ -d /usr/lib/x86_64-linux-gnu/openmpi/include/ ] && echo "exists"), exists) $(info ✓ OpenMPI found, OK to train with multiple GPUs) NVCC_INCLUDES += -I/usr/lib/x86_64-linux-gnu/openmpi/include NVCC_LDFLAGS += -L/usr/lib/x86_64-linux-gnu/openmpi/lib/ NVCC_LDLIBS += -lmpi -lnccl NVCC_FLAGS += -DMULTI_GPU else $(info ✗ OpenMPI is not found, disabling multi-GPU support) $(info ---> On Linux you can try install OpenMPI with `sudo apt install openmpi-bin openmpi-doc libopenmpi-dev`) endif endif endif # Precision settings, default to bf16 but ability to override PRECISION ?= BF16 VALID_PRECISIONS := FP32 FP16 BF16 ifeq ($(filter $(PRECISION),$(VALID_PRECISIONS)),) $(error Invalid precision $(PRECISION), valid precisions are $(VALID_PRECISIONS)) endif ifeq ($(PRECISION), FP32) PFLAGS = -DENABLE_FP32 else ifeq ($(PRECISION), FP16) PFLAGS = -DENABLE_FP16 else PFLAGS = -DENABLE_BF16 endif # PHONY means these targets will always be executed .PHONY: all train_gpt2 test_gpt2 train_gpt2cu test_gpt2cu train_gpt2fp32cu test_gpt2fp32cu profile_gpt2cu # Add targets TARGETS = train_gpt2 test_gpt2 # Conditional inclusion of CUDA targets ifeq ($(NVCC),) $(info ✗ nvcc not found, skipping GPU/CUDA builds) else $(info ✓ nvcc found, including GPU/CUDA support) TARGETS += train_gpt2cu test_gpt2cu train_gpt2fp32cu test_gpt2fp32cu endif $(info ---------------------------------------------) all: $(TARGETS) train_gpt2: train_gpt2.c $(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS) $< $(LDLIBS) $(OUTPUT_FILE) test_gpt2: test_gpt2.c $(CC) $(CFLAGS) $(INCLUDES) $(LDFLAGS) $< $(LDLIBS) $(OUTPUT_FILE) train_gpt2cu: train_gpt2.cu $(NVCC) $(NVCC_FLAGS) $(PFLAGS) $< $(NVCC_LDFLAGS) $(NVCC_INCLUDES) $(NVCC_LDLIBS) $(NVCC_LDFLAGS) $(CUDA_OUTPUT_FILE) train_gpt2fp32cu: train_gpt2_fp32.cu $(NVCC) $(NVCC_FLAGS) $< $(NVCC_LDFLAGS) $(NVCC_INCLUDES) $(NVCC_LDLIBS) $(NVCC_LDFLAGS) $(CUDA_OUTPUT_FILE) test_gpt2cu: test_gpt2.cu $(NVCC) $(NVCC_FLAGS) $(PFLAGS) $< $(NVCC_LDFLAGS) $(NVCC_INCLUDES) $(NVCC_LDLIBS) $(NVCC_LDFLAGS) $(CUDA_OUTPUT_FILE) test_gpt2fp32cu: test_gpt2_fp32.cu $(NVCC) $(NVCC_FLAGS) $< $(NVCC_LDFLAGS) $(NVCC_INCLUDES) $(NVCC_LDLIBS) $(NVCC_LDFLAGS) $(CUDA_OUTPUT_FILE) profile_gpt2cu: profile_gpt2.cu $(NVCC) $(NVCC_FLAGS) $(PFLAGS) -lineinfo $< $(NVCC_LDFLAGS) $(CUDA_OUTPUT_FILE) clean: $(REMOVE_FILES) $(TARGETS)