Packages
llama_cpp_ex
0.8.44
0.8.44
0.8.43
0.8.42
0.8.41
0.8.39
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.0
0.6.14
0.6.13
0.6.12
0.6.11
0.6.10
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.3.0
0.2.0
Elixir bindings for llama.cpp — run LLMs locally with Metal, CUDA, Vulkan, or CPU acceleration.
Current section
Files
Jump to
Current section
Files
llama_cpp_ex
Makefile
Makefile
# Makefile for llama_cpp_ex NIF
# Called by elixir_make during `mix compile`
# elixir_make always sets MIX_APP_PATH. A human running `make` directly does not,
# and an empty value is silently destructive rather than an error: PREFIX becomes
# /priv and BUILD becomes /obj, so `make clean` would try to rm -rf /obj and
# `make rpc-server` cmake-configures into it (observed: "Unable to (re)create the
# private pkgRedirects directory: /obj/rpc_server_build/CMakeFiles/pkgRedirects").
# Falling back keeps every path inside the project. The directory name is not one
# of mix's environments on purpose -- it must not collide with a real build tree,
# and scripts/spark/rpc-worker.sh globs _build/*/lib/llama_cpp_ex so it is still
# found. Reading a variable (`make print-VAR`) never needed this.
# `?=` would miss the defined-but-empty case, which is the dangerous one.
ifeq ($(strip $(MIX_APP_PATH)),)
MIX_APP_PATH := _build/standalone/lib/llama_cpp_ex
endif
PREFIX = $(MIX_APP_PATH)/priv
BUILD = $(MIX_APP_PATH)/obj
NIF_SO = $(PREFIX)/llama_cpp_ex_nif.so
# --- llama.cpp source tree ---------------------------------------------------
# A git checkout has this as the `vendor/llama.cpp` submodule. A Hex tarball does
# not: shipping the tree would add hundreds of MB to the package, so `files:` in
# mix.exs ships `.gitmodules` instead and the rule near the bottom of this file
# clones the tree on demand, pinned to LLAMA_COMMIT.
LLAMA_DIR = $(shell pwd)/vendor/llama.cpp
# Upstream URL, read from .gitmodules so there is a single source of truth. The
# literal is only a fallback for the case where .gitmodules is missing.
LLAMA_REPO := $(shell git config --file .gitmodules --get submodule.vendor/llama.cpp.url 2>/dev/null)
ifeq ($(strip $(LLAMA_REPO)),)
LLAMA_REPO := https://github.com/ggml-org/llama.cpp
endif
# Pinned llama.cpp commit, used when vendor/llama.cpp has to be cloned. MUST
# match the vendor/llama.cpp submodule; bump both together, see
# docs/release-guide.md. Override to build the NIF against another revision.
LLAMA_COMMIT ?= 9e40df63ba151d771d8b247ac4011cf203337e99
# The commit actually on disk. A submodule can be bumped without LLAMA_COMMIT
# following it, and the build has to key off what is really there.
LLAMA_SHA := $(shell test -e $(LLAMA_DIR)/.git && git -C $(LLAMA_DIR) rev-parse HEAD 2>/dev/null || echo $(LLAMA_COMMIT))
LLAMA_SHA_SHORT := $(shell echo $(LLAMA_SHA) | cut -c1-12)
# Compiler
CXX ?= c++
# -DNDEBUG matches the llama.cpp libraries this object links against: they are
# configured with CMAKE_BUILD_TYPE=Release, whose CMAKE_CXX_FLAGS_RELEASE is
# "-O3 -DNDEBUG". Without it the llama.cpp/ggml/common headers inlined into this
# translation unit keep their debug assertions live, and an assert() firing
# inside a NIF takes down the whole VM. Build consistency is the entire reason.
#
# It does NOT disarm GGML_ASSERT. That expands to an unconditional ggml_abort
# (vendor/llama.cpp/ggml/include/ggml.h) and was observed aborting the VM with
# NDEBUG active; only explicit validation at the NIF boundary prevents those.
CXXFLAGS = -std=c++17 -O2 -DNDEBUG -fPIC -fvisibility=hidden -Wall -Wno-unused-parameter -Wno-unused-function
CXXFLAGS += -I$(ERTS_INCLUDE_DIR)
CXXFLAGS += -I$(FINE_INCLUDE_DIR)
CXXFLAGS += -I$(LLAMA_DIR)/include
CXXFLAGS += -I$(LLAMA_DIR)/ggml/include
CXXFLAGS += -I$(LLAMA_DIR)/common
CXXFLAGS += -I$(LLAMA_DIR)/vendor
# Linker
LDFLAGS = -shared
# Platform detection
UNAME_S := $(shell uname -s)
# --- CUDA toolkit discovery --------------------------------------------------
# Resolved once, up here, because two separate decisions depend on it: `auto`
# uses it to decide whether CUDA is available at all, and the Linux link line
# below needs the library directory.
#
# Probing only `which nvcc` was wrong in both places. nvcc is routinely absent
# from a *non-login* PATH -- DGX OS installs it via /etc/profile.d/nv_paths.sh,
# which `ssh host make`, systemd units and most CI shells never source -- so on
# a machine with a complete toolkit `auto` silently produced a CPU-only build,
# and an explicit LLAMA_BACKEND=cuda produced no -L flags and failed the link on
# libcudart. Honour CUDA_HOME and CUDA_PATH first (both are conventional and
# either may be exported by a module system), then nvcc on PATH, then the
# standard install locations.
ifeq ($(strip $(CUDA_HOME)),)
CUDA_HOME := $(strip $(CUDA_PATH))
endif
ifeq ($(strip $(CUDA_HOME)),)
CUDA_HOME := $(patsubst %/bin/nvcc,%,$(shell command -v nvcc 2>/dev/null))
endif
ifeq ($(strip $(CUDA_HOME)),)
CUDA_HOME := $(firstword $(wildcard /usr/local/cuda /opt/cuda) \
$(shell ls -d /usr/local/cuda-* 2>/dev/null | sort -V | tail -1))
endif
# Presence of the compiler, not of the directory: /usr/local/cuda survives a
# partial uninstall, and a runtime-only install has libraries but cannot build.
NVCC := $(wildcard $(CUDA_HOME)/bin/nvcc)
# x86_64 and sbsa toolkits both expose lib64 (a symlink to targets/<triple>/lib
# on sbsa); Debian's packaged nvidia-cuda-toolkit only has lib.
CUDA_LIBDIR := $(firstword $(wildcard $(CUDA_HOME)/lib64 $(CUDA_HOME)/lib))
# ggml's own GGML_CUDA_NCCL defaults to ON and quietly links libnccl through
# cmake whenever NCCL happens to be installed on the build host. cmake's
# target_link_libraries is invisible to this file -- the link line below is
# assembled by hand from the static archives ggml leaves behind -- so on a host
# with NCCL (every DGX, most multi-GPU boxes) ggml-cuda.a came out carrying
# undefined nccl* symbols and the resulting NIF failed to load with
# `undefined symbol: ncclAllReduce`.
#
# So whether NCCL is used is declared here rather than discovered, and it is off
# by default. It only accelerates collectives across multiple GPUs, while
# linking it makes libnccl.so.2 a hard load-time requirement of the artifact --
# the same trade as -march=native, and the same answer.
LLAMA_CUDA_NCCL ?= 0
# The same discover-vs-declare hazard, one layer down and with a second flag
# chained to it.
#
# ggml probes the host CPU with -mcpu=native (ggml/src/ggml-cpu/CMakeLists.txt)
# whenever GGML_NATIVE is ON, which is the default. On GB10 with GCC 13.3 that
# probe fails *silently* -- the compiler predates Cortex-X925/A725 and rejects
# -mcpu=cortex-x925, cmake emits a soft warning and exits 0, and the CPU backend
# is quietly built at base ARMv8-A. Measured on the emitted libggml-cpu.a:
# 0 sdot, 0 smmla, no SVE, versus 1134 sdot + 370 smmla + SVE once the
# architecture is named. Those are the Q4/Q8 quantized matmul kernels, so this
# is not a rounding error.
#
# Naming it requires GGML_NATIVE=OFF, and that is the trap: with GGML_NATIVE
# off, ggml-cuda stops using `native` and falls back to a seven-architecture fat
# binary (ggml/src/ggml-cuda/CMakeLists.txt:27-28), turning a 1m50s build into a
# ~6x one for no benefit on a machine with exactly one known GPU. So the two
# move together or not at all, and setting one without the other is an error
# rather than a surprise six minutes later.
#
# DGX Spark (GB10):
# LLAMA_CPU_ARM_ARCH=armv9.2-a+dotprod+i8mm+fp16+bf16+sve2
# LLAMA_CUDA_ARCH=121a-real
LLAMA_CPU_ARM_ARCH ?=
LLAMA_CUDA_ARCH ?=
# The ggml RPC backend, which lets a model's layers live on another host. Off by
# default: it is a networked attack surface and a protocol version coupling, and
# neither belongs in a build nobody asked for.
#
# GGML_RPC_RDMA gets the same declare-don't-discover treatment as NCCL, for
# exactly the same reason. ggml/src/ggml-rpc/CMakeLists.txt:11-22 turns it ON
# whenever libibverbs happens to be installed on the build host, so the same
# source produces a different artifact on a DGX than on a laptop, silently. And
# because that file links ibverbs with target_link_libraries -- invisible to the
# hand-assembled link line below -- libggml-rpc.a comes out carrying undefined
# ibv_* symbols and the NIF fails to load. Same failure mode as
# `undefined symbol: ncclAllReduce`, same fix: declare it here and pair the link
# flag.
LLAMA_RPC ?= 0
LLAMA_RPC_RDMA ?= 1
# Backend selection (auto, metal, cuda, vulkan, cpu)
LLAMA_BACKEND ?= auto
# CMake flags
CMAKE_FLAGS = -DCMAKE_BUILD_TYPE=Release
CMAKE_FLAGS += -DBUILD_SHARED_LIBS=OFF
CMAKE_FLAGS += -DLLAMA_BUILD_EXAMPLES=OFF
CMAKE_FLAGS += -DLLAMA_BUILD_TESTS=OFF
CMAKE_FLAGS += -DLLAMA_BUILD_SERVER=OFF
CMAKE_FLAGS += -DLLAMA_BUILD_TOOLS=OFF
CMAKE_FLAGS += -DLLAMA_BUILD_APP=OFF
CMAKE_FLAGS += -DLLAMA_OPENSSL=OFF
CMAKE_FLAGS += -DCMAKE_POSITION_INDEPENDENT_CODE=ON
# Backend configuration
ifeq ($(LLAMA_BACKEND),auto)
ifeq ($(UNAME_S),Darwin)
CMAKE_FLAGS += -DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON
else
ifneq ($(NVCC),)
CMAKE_FLAGS += -DGGML_CUDA=ON
endif
endif
else ifeq ($(LLAMA_BACKEND),metal)
CMAKE_FLAGS += -DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON
else ifeq ($(LLAMA_BACKEND),cuda)
CMAKE_FLAGS += -DGGML_CUDA=ON
else ifeq ($(LLAMA_BACKEND),vulkan)
CMAKE_FLAGS += -DGGML_VULKAN=ON
else ifeq ($(LLAMA_BACKEND),cpu)
CMAKE_FLAGS += -DGGML_METAL=OFF -DGGML_CUDA=OFF -DGGML_VULKAN=OFF
endif
# cmake locates the toolkit by searching PATH for nvcc, so it has the same blind
# spot the discovery block above exists to cover: without this, a build that
# correctly selected CUDA still fails in `find_package(CUDAToolkit)`.
ifneq (,$(filter -DGGML_CUDA=ON,$(CMAKE_FLAGS)))
ifneq ($(NVCC),)
CMAKE_FLAGS += -DCMAKE_CUDA_COMPILER=$(NVCC)
endif
# Always stated, never left to ggml's default, so the archives cmake produces
# and the link line assembled below cannot disagree about NCCL.
ifneq ($(filter 1 true yes,$(LLAMA_CUDA_NCCL)),)
CMAKE_FLAGS += -DGGML_CUDA_NCCL=ON
else
CMAKE_FLAGS += -DGGML_CUDA_NCCL=OFF
endif
endif
# Outside the CUDA block on purpose: RPC is backend-independent, and a CPU-only
# worker node is a legitimate configuration. Both states are stated explicitly
# so the cmake configuration and the link line below provably agree.
ifneq ($(filter 1 true yes,$(LLAMA_RPC)),)
CMAKE_FLAGS += -DGGML_RPC=ON
# ggml/src/CMakeLists.txt:318-321 puts GGML_USE_RPC on the cmake `ggml` target
# as a PUBLIC definition. The NIF is compiled by this file with a hand-written
# g++ line that carries only -I flags, so it never inherits cmake target
# properties and would compile its #ifdef GGML_USE_RPC blocks out while
# linking against a libggml-rpc.a that is definitely there. State it.
CXXFLAGS += -DGGML_USE_RPC
# RDMA is Linux-only upstream (the CMakeLists forces it off elsewhere), so
# asking for it on macOS would be a configure-time lie.
ifeq ($(UNAME_S),Linux)
ifneq ($(filter 1 true yes,$(LLAMA_RPC_RDMA)),)
CMAKE_FLAGS += -DGGML_RPC_RDMA=ON
else
CMAKE_FLAGS += -DGGML_RPC_RDMA=OFF
endif
else
CMAKE_FLAGS += -DGGML_RPC_RDMA=OFF
endif
else
CMAKE_FLAGS += -DGGML_RPC=OFF
endif
# Portable builds, for artifacts that leave this machine. ggml defaults
# GGML_NATIVE to ON unless cross-compiling (vendor/llama.cpp/ggml/CMakeLists.txt),
# which adds -march=native (ggml/src/ggml-cpu/CMakeLists.txt) and ties the binary
# to the build machine's CPU. That is free performance for a local build and a
# SIGILL waiting to happen in a published one, so the precompile workflow sets
# LLAMA_PORTABLE=1 while local builds keep -march=native.
ifneq ($(filter 1 true yes,$(LLAMA_PORTABLE)),)
CMAKE_FLAGS += -DGGML_NATIVE=OFF
LLAMA_PORTABLE_SUFFIX = -portable
endif
# The arch pair, emitted after LLAMA_PORTABLE so the two GGML_NATIVE=OFF
# sources are visible together. LLAMA_PORTABLE already turns native off for a
# different reason -- artifacts that leave this machine, on release runners with
# no GPU and therefore no CUDA arch to pin -- so the filter below is what keeps
# the two from double-emitting the flag.
ifneq ($(strip $(LLAMA_CPU_ARM_ARCH)),)
ifeq ($(strip $(LLAMA_CUDA_ARCH)),)
ifneq (,$(filter -DGGML_CUDA=ON,$(CMAKE_FLAGS)))
$(error LLAMA_CPU_ARM_ARCH requires GGML_NATIVE=OFF, which drops ggml-cuda \
from one architecture to a seven-architecture fat binary. Set \
LLAMA_CUDA_ARCH too (DGX Spark GB10: LLAMA_CUDA_ARCH=121a-real), or \
unset LLAMA_CPU_ARM_ARCH)
endif
endif
ifeq (,$(filter -DGGML_NATIVE=OFF,$(CMAKE_FLAGS)))
CMAKE_FLAGS += -DGGML_NATIVE=OFF
endif
CMAKE_FLAGS += -DGGML_CPU_ARM_ARCH=$(LLAMA_CPU_ARM_ARCH)
endif
ifneq ($(strip $(LLAMA_CUDA_ARCH)),)
ifneq (,$(filter -DGGML_CUDA=ON,$(CMAKE_FLAGS)))
CMAKE_FLAGS += -DCMAKE_CUDA_ARCHITECTURES=$(LLAMA_CUDA_ARCH)
endif
endif
# Both of the above select a different set of emitted instructions from the same
# sources, so they belong in the build-directory key alongside the backend --
# otherwise toggling one reuses the previous CMakeCache.txt and silently
# no-ops, which is the exact failure the key exists to prevent. A short hash
# keeps the directory name readable; empty values hash to nothing so today's
# unflagged builds keep their existing directory and stay cache hits.
# Immediate assignment: a recursive one would re-run the shell on every
# expansion of LLAMA_BUILD.
ifneq ($(strip $(LLAMA_CPU_ARM_ARCH))$(strip $(LLAMA_CUDA_ARCH)),)
LLAMA_ARCH_SUFFIX := -$(shell printf '%s|%s' '$(LLAMA_CPU_ARM_ARCH)' '$(LLAMA_CUDA_ARCH)' \
| { shasum -a 256 2>/dev/null || sha256sum; } | cut -c1-8)
endif
# RPC adds a whole backend library plus the public GGML_USE_RPC define, and the
# RDMA toggle changes the code inside it, so both are part of the key too.
# Spelled out rather than hashed: these two show up in every two-node runbook
# and a readable directory name is worth more than eight characters.
ifneq ($(filter 1 true yes,$(LLAMA_RPC)),)
ifneq (,$(filter -DGGML_RPC_RDMA=ON,$(CMAKE_FLAGS)))
LLAMA_RPC_SUFFIX = -rpc
else
LLAMA_RPC_SUFFIX = -rpc-tcp
endif
endif
# Custom CMake args
ifdef LLAMA_CMAKE_ARGS
CMAKE_FLAGS += $(LLAMA_CMAKE_ARGS)
endif
# Build layout. Every key here is load-bearing, because each one selects a
# different cmake configuration or a different set of sources: reusing one build
# tree across them is what made submodule bumps and backend switches silently
# no-op. The directory carries the backend, portability, the architecture flags
# and RPC, so a switch gets a clean CMakeCache.txt (and switching back is still
# a cache hit); the stamp carries the llama.cpp commit, so a bump forces a
# rebuild in place.
LLAMA_BUILD = $(BUILD)/llama_build-$(LLAMA_BACKEND)$(LLAMA_PORTABLE_SUFFIX)$(LLAMA_ARCH_SUFFIX)$(LLAMA_RPC_SUFFIX)
LLAMA_STAMP = $(LLAMA_BUILD)/.built-$(LLAMA_SHA_SHORT)
# Platform-specific linker flags
ifeq ($(UNAME_S),Darwin)
LDFLAGS += -undefined dynamic_lookup
LDFLAGS += -framework Foundation -framework Accelerate
ifneq (,$(filter -DGGML_METAL=ON,$(CMAKE_FLAGS)))
LDFLAGS += -framework Metal -framework MetalKit
endif
else
LDFLAGS += -lstdc++ -lm -lpthread
# ggml-cpu uses OpenMP on Linux when available
ifneq ($(shell $(CXX) -fopenmp -E - < /dev/null 2>/dev/null && echo yes),)
LDFLAGS += -lgomp
endif
# Pairs with -DGGML_RPC_RDMA=ON above. ggml-rpc's CMakeLists links ibverbs
# with target_link_libraries, which this hand-assembled link line never sees,
# so without this libggml-rpc.a's undefined ibv_* symbols surface as a NIF
# that will not dlopen.
ifneq (,$(filter -DGGML_RPC_RDMA=ON,$(CMAKE_FLAGS)))
LDFLAGS += -libverbs
endif
# ggml-cuda.a leaves the CUDA runtime, cuBLAS/cuBLASLt and the CUDA driver API
# unresolved, but this line only ever added -lstdc++ -lm -lpthread. The .so
# then linked and failed at load with `undefined symbol: cuMemCreate`, a
# driver-API symbol ggml-cuda's VMM pool calls.
ifneq (,$(filter -DGGML_CUDA=ON,$(CMAKE_FLAGS)))
ifeq ($(strip $(CUDA_LIBDIR)),)
$(error CUDA backend selected but no CUDA toolkit libraries were found. \
Set CUDA_HOME to the toolkit root, the directory holding bin/nvcc.)
endif
LDFLAGS += -L$(CUDA_LIBDIR)
# -lcuda is the driver API, which is shipped by the driver and not by the
# toolkit, so it is missing on every GPU-less build host including the
# release runners. The stub carries SONAME libcuda.so.1, so linking against
# it resolves the symbols at build time and still loads the real driver
# library at run time.
ifneq ($(wildcard $(CUDA_LIBDIR)/stubs),)
LDFLAGS += -L$(CUDA_LIBDIR)/stubs
endif
LDFLAGS += -lcudart -lcublas -lcublasLt -lcuda
# Matches the -DGGML_CUDA_NCCL above. Opting in without this pairing is the
# `undefined symbol: ncclAllReduce` load failure.
ifneq ($(filter 1 true yes,$(LLAMA_CUDA_NCCL)),)
LDFLAGS += -lnccl
endif
endif
endif
# CPU count for parallel builds
NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)
# Sources
NIF_SRC = c_src/llama_cpp_ex/llama_nif.cpp
NIF_OBJ = $(BUILD)/llama_nif.o
# Everything downstream depends on the build *configuration*, not only on file
# timestamps, and three separate rules were blind to it:
#
# - Toggling LLAMA_RPC changes CXXFLAGS (-DGGML_USE_RPC) and the archive set
# while touching no source file, so make kept a previously linked .so.
# Observed twice: once as `Function not found model_load/11`, once as an RPC
# build reporting :rpc_unsupported at runtime. Silent, and it looks like an
# Elixir bug.
# - LDFLAGS changes (-lnccl, -libverbs) are invisible to every source rule.
# - A CMAKE_FLAGS change that does not alter the build directory -- NCCL, for
# instance -- left $(LLAMA_STAMP) newer than CMakeLists.txt, so cmake was
# never re-run and the archives kept the previous configuration.
#
# This stamp's *name* carries a hash of all three, so any change makes the
# prerequisite disappear and the cmake configure, the compile and the link all
# rerun. cmake is incremental, so the cost of a reconfigure is small and the
# alternative is a build that silently does not match its flags.
LLAMA_CONFIG_HASH := $(shell printf '%s|%s|%s|%s' \
'$(CXXFLAGS)' '$(LDFLAGS)' '$(CMAKE_FLAGS)' '$(LLAMA_BUILD)' \
| { shasum -a 256 2>/dev/null || sha256sum; } | cut -c1-12)
LLAMA_CONFIG_STAMP = $(BUILD)/.config-$(LLAMA_CONFIG_HASH)
# ...and one more marker, next to the artifact rather than next to the objects,
# because the artifact is SHARED and gets overwritten from two directions.
#
# Mix symlinks $(MIX_APP_PATH)/priv to the project's priv/ in every MIX_ENV, so
# dev, test and bench all write one llama_cpp_ex_nif.so while each keeps its own
# objects and its own llama.cpp tree. Two consequences, both observed:
#
# 1. Build test with LLAMA_RPC=1 and bench without it, and whichever ran last
# is what every environment loads. The plain timestamp rule then sees a .so
# newer than its own object and skips the relink, so it stays wrong --
# a test suite reporting {:error, :rpc_unsupported} against a live RPC
# worker the same tree had just talked to.
# 2. Mix restores a downloaded precompiled artifact into priv/ and copies it
# over the source build, with a fresh mtime, so no timestamp comparison can
# see it either.
#
# So the marker records what the artifact IS, not when it was written: the
# configuration hash plus a digest of the bytes that were linked. Anything that
# replaces the artifact -- another MIX_ENV, a downloaded release, a stray cp --
# breaks the digest and forces a relink.
NIF_LINK_STAMP = $(PREFIX)/.llama_cpp_ex_nif.built
# The file is an argument, not stdin: a redirect here would attach to `cut`.
NIF_DIGEST = { shasum -a 256 "$(NIF_SO)" 2>/dev/null || sha256sum "$(NIF_SO)"; } | cut -c1-32
# Targets
.PHONY: all clean rpc-server check-artifact
# Serial, so check-artifact provably runs before the link decision below.
# Nothing here benefits from make's -j: the heavy build is cmake's own
# `--build -j$(NPROC)`.
.NOTPARALLEL:
all: check-artifact $(NIF_LINK_STAMP)
check-artifact:
@if [ -f "$(NIF_LINK_STAMP)" ]; then \
want="$(LLAMA_CONFIG_HASH) $$($(NIF_DIGEST))"; \
if [ "$$(cat "$(NIF_LINK_STAMP)")" != "$$want" ]; then \
echo "==> $(NIF_SO) does not match this build; relinking"; \
rm -f "$(NIF_LINK_STAMP)"; \
fi; \
fi
# Upstream's standalone RPC worker. Not the production path -- LlamaCppEx.RPC.Server
# hosts the same server inside the NIF, under OTP supervision -- but when a
# two-node run misbehaves the first question is "is this us or upstream", and
# having both answers costs 1m32s of build time.
#
# Its own build directory, because it needs LLAMA_BUILD_TOOLS=ON and the NIF's
# tree deliberately does not build tools. The cmake target is `ggml-rpc-server`,
# not `rpc-server` (vendor/llama.cpp/tools/rpc/CMakeLists.txt:1).
RPC_SERVER_BUILD = $(BUILD)/rpc_server_build
RPC_SERVER_BIN = $(RPC_SERVER_BUILD)/bin/ggml-rpc-server
rpc-server: $(RPC_SERVER_BIN)
@echo "built $(RPC_SERVER_BIN)"
$(RPC_SERVER_BIN): $(LLAMA_DIR)/CMakeLists.txt
@test -n "$(filter 1 true yes,$(LLAMA_RPC))" || { \
echo "error: rpc-server needs LLAMA_RPC=1"; exit 1; }
cmake -B $(RPC_SERVER_BUILD) -S $(LLAMA_DIR) $(CMAKE_FLAGS) -DLLAMA_BUILD_TOOLS=ON
cmake --build $(RPC_SERVER_BUILD) --config Release -j$(NPROC) --target ggml-rpc-server
# `make print-CMAKE_FLAGS` echoes one variable, fully expanded. `make -p` cannot
# do this: CMAKE_FLAGS is a recursive variable, so the database dump shows the
# unexpanded text. test/makefile_arch_flags_test.exs asserts on what this build
# will really hand to cmake, and that needs the expansion.
print-%:
@echo '$($*)'
# Materialize vendor/llama.cpp when it is absent, which is the Hex-tarball case:
# no vendor/ directory and no surrounding git repository. Pinned to LLAMA_COMMIT
# so the tree matches the submodule a git checkout would use. GitHub serves
# arbitrary SHAs, so `init` + `fetch --depth 1 <sha>` gets exactly that commit
# without cloning any history.
$(LLAMA_DIR)/CMakeLists.txt:
@command -v git >/dev/null 2>&1 || { \
echo "error: vendor/llama.cpp is missing and git is not in PATH."; \
echo " Install git, or place a llama.cpp checkout at vendor/llama.cpp."; \
exit 1; }
@if [ -e $(LLAMA_DIR)/.git ]; then \
echo "error: $(LLAMA_DIR) exists as a git checkout but has no CMakeLists.txt."; \
echo " In a git checkout the submodule is not fully initialized:"; \
echo " git submodule update --init --recursive"; \
echo " Otherwise a previous clone was interrupted; remove the tree and"; \
echo " rebuild: rm -rf $(LLAMA_DIR)"; \
exit 1; \
fi
@echo "==> vendor/llama.cpp not found; cloning $(LLAMA_REPO) at $(LLAMA_COMMIT)"
rm -rf $(LLAMA_DIR)
@mkdir -p $(dir $(LLAMA_DIR))
git -c init.defaultBranch=master init -q $(LLAMA_DIR)
git -C $(LLAMA_DIR) remote add origin $(LLAMA_REPO)
git -C $(LLAMA_DIR) fetch --depth 1 --quiet origin $(LLAMA_COMMIT)
git -C $(LLAMA_DIR) checkout --quiet FETCH_HEAD
@test -f $@ || { echo "error: clone finished but $@ is still missing"; exit 1; }
# Build llama.cpp static libraries
$(LLAMA_STAMP): $(LLAMA_DIR)/CMakeLists.txt $(LLAMA_CONFIG_STAMP)
@mkdir -p $(LLAMA_BUILD)
cmake -B $(LLAMA_BUILD) -S $(LLAMA_DIR) $(CMAKE_FLAGS)
cmake --build $(LLAMA_BUILD) --config Release -j$(NPROC)
@rm -f $(LLAMA_BUILD)/.built-*
@touch $@
# Regenerated whenever the configuration hash changes, which is what forces the
# compile and the link below to rerun. Old stamps are swept so $(BUILD) does not
# accumulate one per configuration ever tried.
$(LLAMA_CONFIG_STAMP):
@mkdir -p $(dir $@)
@rm -f $(BUILD)/.config-*
@touch $@
# Compile NIF
$(NIF_OBJ): $(NIF_SRC) c_src/llama_cpp_ex/llama_nif.h $(LLAMA_STAMP) $(LLAMA_CONFIG_STAMP)
@mkdir -p $(dir $@)
$(CXX) $(CXXFLAGS) -c $(NIF_SRC) -o $@
# Link NIF - find all static libs from llama.cpp build.
# The target is the marker, not the .so: see NIF_LINK_STAMP above for why the
# artifact's own timestamp cannot be trusted.
$(NIF_LINK_STAMP): $(NIF_OBJ) $(LLAMA_STAMP) $(LLAMA_CONFIG_STAMP)
@mkdir -p $(PREFIX)
@LIBS=$$(find $(LLAMA_BUILD) -name '*.a' \
! -path '*/CMakeFiles/*' \
! -path '*/examples/*' \
! -path '*/tests/*' \
| sort); \
if [ "$(UNAME_S)" = "Linux" ]; then \
$(CXX) $(NIF_OBJ) -Wl,--start-group $$LIBS -Wl,--end-group $(LDFLAGS) -o $(NIF_SO); \
else \
$(CXX) $(NIF_OBJ) $$LIBS $(LDFLAGS) -o $(NIF_SO); \
fi
@printf '%s %s' '$(LLAMA_CONFIG_HASH)' "$$($(NIF_DIGEST))" > $@
clean:
rm -rf $(BUILD) $(PREFIX)/llama_cpp_ex_nif.so $(NIF_LINK_STAMP)