🦙 llama.cpp 图解教程llama.cpp Visual Guide 第一部分 · 宏观全景Part 1 · The Big Picture 02 / 40
第一部分 · 宏观全景Part 1 · The Big Picture

项目全景地图The project map

第一次打开 llama.cpp,几百个文件、十几个目录,很容易发懵:到底先看哪儿?其实它分层非常清晰,每个目录各司其职。 这一课先给你一张"校园地图"——认清顶层目录在干什么,再把它们对回上一课的"四层",接着看清一个模型从训练到运行 要穿过哪几个目录、边界落在哪里。读完这张图,无论你是想跑工具还是钻源码,都不会迷路。

🔌 生活类比
把整个仓库想成一座工厂园区,而目录就是地图:每个车间(目录)只干一件事—— 有的造引擎(ggml)、有的负责装配(src/llama-*)、有的是对外门店(tools/), 还有的是把原料运进园区的码头convert_*.py 把外面的模型转成 .gguf)。 先认地图、看清车间之间怎么衔接,比一头扎进某个车间更重要。

顶层目录速览

站在仓库根目录,先别急着点开文件,按"这个目录到底在干什么"把主要目录扫一遍。下面这张表就是地图的图例:

目录作用
ggml/自研张量引擎:张量 · 计算图 · 算子 · 后端调度;独立子项目(自带 include/src/),是整个项目最底层、也最"硬"的一块
ggml/src/ggml-cpu · ggml-cuda · ggml-metal · ggml-vulkan …硬件后端,把算子真正算在 CPU / GPU 上(还有 hip / sycl / musa / opencl 等十余种)
src/llama 推理库:把引擎拼成"会话级"推理——llama-model-loader · llama-graph · llama-kv-cache · llama-sampler · llama-vocab · llama-chat · llama-grammar · llama-quant
include/公共 C APIllama.h(唯一对外契约);llama-cpp.h(C++ 薄封装 + RAII)
common/复用工具 / 胶水arg(参数解析)· sampling(采样封装)· chat · log · download · json-schema-to-grammar …;给程序用,不是推理库本体
tools/可执行程序llama-cli · llama-server · llama-quantize · llama-mtmd-cli(多模态)· llama-perplexity · llama-bench
examples/小型示例程序:simple 用两百多行演示最小推理,是最佳"可读"入口
gguf-py/Python 的 GGUF 读写库:转换脚本靠它写出 .gguf
convert_hf_to_gguf.py 等Python 转换脚本(共 4 个 convert_*.py:3 个转换器 + 1 个 tokenizer 哈希维护脚本;主力是 HuggingFace -> GGUF)
models/ · tests/ · docs/ · grammars/ · cmake/模型数据 / 测试 / 文档 / GBNF 示例 / 构建系统

一个简单的判断法:越往下越"硬"——ggml/ 偏数学与硬件,src/ 偏模型与会话逻辑,tools/common/ 偏"给人用"。而真正对外暴露的,自始至终只有 include/llama.h 这一个公共头文件。

四块各管一段:为什么要拆开

目录虽多,真正的主角只有四块,每块咬死一件事、互不越界。先认清这四条边界,再回头看那张目录表,剩下的就全是细节了:

这一块只干一件事大致落在
引擎造算力:定义张量、把运算组织成计算图、调度到各后端真正算出来ggml/
推理库把算力拼成"会话":加载权重、搭图、KV cache、采样、分词、聊天模板src/llama-*
程序 / 胶水给人用:命令行、HTTP 服务、量化器,外加把库粘成程序的通用件tools/ · common/
模型准备把外部模型搬进来:HuggingFace 权重转成 .ggufgguf-py/ · convert_*.py

为什么非要拆这么开?因为这四件事的变化节奏完全不同:后端算子要追着新硬件、新指令集不断改;推理逻辑要随新模型结构演进;命令行参数与服务接口随用户需求增删;转换脚本则要跟着上游模型格式跑。把它们焊死在一起,改一处就得提心吊胆会不会崩另一处。拆开之后,各自独立演进、独立测试、独立复用——给 ggml 加一个新后端,不必动 src/llama-* 一行;新增一个模型结构,也碰不到底层算子。这就是"边界清晰"最实在的回报。

为什么公共 API 只留一个头文件

还有个刻意的设计值得单拎出来:上百个内部文件里,真正对外公开的只有 include/llama.h 一个头。把对外的口子收得这么窄,换来三重好处:其一,内部随便改——只要 llama.h 里的函数签名不变,src/llama-* 内部怎么重构、换数据结构、调算法,都不会惊动外面的使用者;其二,对外契约稳定,使用方升级版本时心里有底,不必追着内部细节东奔西跑;其三,因为暴露的是一套 C ABI,几乎所有语言都能绑定——Python、Go、Rust、Node 等都能透过这个 C 接口调用引擎,社区里大量的语言绑定正是这么搭起来的。把对外收成一个小口,内部才换来放手重构的自由。

顺带提一句:紧挨着 llama.h 还有个 llama-cpp.h,它只是给 C++ 用户的一层薄封装——用 RAII(智能指针)自动管理 llama_model / llama_context 的释放,省去手动 free。它并不扩大对外暴露面,只是把同一个 C 接口包得更顺手,所以"对外只有一个契约"这句话依然成立。

它们怎么对上"四层"

把上面这些目录映射回上一课的"四层"结构,就一目了然:

工具 / 应用tools/ · examples/
tools/(cli · server · quantize · mtmd …)、examples/:面向用户的命令行、服务与示例
推理库src/llama-*
src/llama-* 加上对外头文件 include/llama.h:加载 · 计算图 · KV cache · 采样 · 分词 · 聊天模板
引擎ggml
ggml/ggml.c · gguf.cpp · ggml-alloc · ggml-backend):张量 · 计算图 · 算子 · 调度 · GGUF 格式
后端CPU · CUDA · Metal · Vulkan …
ggml/src/ggml-cpu · ggml-cuda · ggml-metal · ggml-vulkan …:把算子真正算在硬件上

除了这条主干,还有两条支线:① 模型准备——gguf-py/convert_*.py(Python)把 HuggingFace 模型转成 .gguf,再喂给引擎;② 配套支撑——common/(把库粘成程序的胶水)以及 tests/ · docs/ · cmake/

为什么叫"支线"?因为一次推理请求只在主干四层里上下穿行,根本不会跑进这两条线:模型准备在跑之前就一次性做完了(产出 .gguf 便退场),配套支撑则像脚手架围在主干周围——common/ 帮程序少写样板,tests/ · docs/ · cmake/ 管测试、文档与构建。把"运行时会经过的"和"运行前 / 运行外的"分清楚,读源码时就不会把脚手架错当成承重墙。

一个模型怎么从训练到运行

把目录串起来看,一个模型的"一生"其实是一条很直的流水线:左边在 Python 里准备,右边在 C++ 里运行,中间靠一个文件交接。顺着这条线走一遍,就知道每个目录在整条链路里站在哪一站:

HF / PyTorch 模型
safetensors 权重
->
convert_hf_to_gguf.py
Python · gguf-py
->
model.gguf
单文件 · 权重 + 元数据
->
llama_model_load_from_file
C++ 运行时
->
跑出字
llama-cli / server

左半截是 Python(准备),右半截是 C++(运行),两者的边界就是中间那个 .gguf 文件。

这条边界很关键:转换脚本只在准备阶段跑一次,产出 .gguf 后就退场;运行时完全不碰 Python,只认这一个文件。所以同一个 .gguf,既能喂给 llama-cli,也能喂给 llama-serverexamples/simple——它们共享同一套加载与推理代码。

把这条流水线落到真实命令上,最常见的就是"转换 -> 量化 -> 运行"三步。下面这段是最小可跑的骨架——左边 Python 准备、右边 C++ 运行,中间仍旧靠那个 .gguf 交接:

# 模型从准备到运行的完整管道
# 1) 转换:HuggingFace 模型 -> GGUF(Python 侧,--outfile 指定输出名)
python convert_hf_to_gguf.py ./my-model --outfile my-model.gguf   # 16 位浮点(默认 auto)
# 2) 量化(可选,压小)
llama-quantize my-model.gguf my-model-Q4.gguf Q4_0
# 3) 运行(C++ 侧)
llama-cli -m my-model-Q4.gguf -p "你好"

三步正好落在三个目录:第一步在 gguf-py/ + convert_*.py(Python)里跑,吐出一个 FP16 的 .gguf;第二步 llama-quantize(来自 tools/)把它压成 Q4_0 这类低位宽版本,体积骤降到约四分之一;第三步 llama-cli 加载量化后的文件,真正"跑出字"。两点值得记牢:量化是可选的——不在意体积,直接拿第一步的 .gguf 去跑也行;而第二、三步全程不碰 Python,只认那一个文件,这正是前面那句"边界就在 .gguf"落到命令上的样子。

convert_hf_to_gguf.py 这一步具体在做什么?它读入 HuggingFace 目录里的 config.json(超参)、分词器与 safetensors(权重),按模型架构把张量改名、必要时转置,再连同元数据一起写成一个 .gguf。换句话说,这条"码头"把外部世界五花八门的模型,统一翻译成引擎只认的那一种格式——之后 C++ 侧就再不必关心它原本长什么样了。

顺带把体积感建立起来:第一步产出的 FP16 文件,7B 模型约 14 GB;第二步 Q4_0 量化后降到约 4 GB——同一条命令链,跑完就把一个"原本要显卡"的模型压成了"普通内存就能装"的文件。这也正呼应上一课那条"从重到轻"的流水线,只是这次落在了真实命令上。

想读源码,从哪进

想读源码,却不知道从哪下手?与其从第一个文件啃到最后一个,不如先想清楚你的目标,再选对应的入口往下钻。常见的三种目标,正好对应三个入口:

想会用tools/ · examples/simple
先把 llama-cli/llama-server 跑起来,再读 examples/simple 的最小调用
懂推理src/llama-*
按主线读:llama-model-loader -> llama-graph -> llama-kv-cache -> llama-sampler
懂算子ggml/
进引擎:ggml.c 与各 ggml-* 后端,看张量/算子/调度怎么实现

如果只想挑一个地方开始,首推 examples/simple:它用两百行左右把"加载模型 -> 分词 -> 解码循环 -> 采样 -> 输出"这条主线完整跑了一遍,没有服务、多模态那些枝节干扰。把它从头读到尾一遍,再把每个调用对回上面四层——这个函数属于推理库还是引擎、走的是 llama.h 里哪个接口——整张地图就从"看过"变成"走过"了。

不管从哪条路进,记住对外只有一个公共契约 include/llama.h:搞不清某个能力归谁管时,先回到这个头文件,看它把哪些函数暴露给了外面。先认入口,再逐层往下钻,比漫无目的地翻文件高效得多。

深入一点(选读)

下面四个问题,想把这张地图看透的同学点开看;只想记住主干的可以先跳过。

1 GGUF 文件里到底装了什么? 点击展开

示例:一个 .gguf 从头到尾大致分四段,按顺序紧挨着排在同一个文件里:

GGUF 文件结构(单文件,按顺序排布)
magic + 版本元数据 KV(超参 / 词表 / chat 模板)张量信息(名 / 形状 / 类型 / 偏移)张量数据(权重块)
加载时按"张量信息"里的偏移,用 mmap 映射到对应"张量数据"块,按需取用、不全量拷贝

逐段拆开看:① 文件头是四字节 magic "GGUF" 加一个版本号(当前为 3),后面记着"有多少个张量、多少条元数据";② 元数据 KV 是一长串键值对——超参如 <arch>.block_count(层数)、<arch>.embedding_length(隐藏维度),词表如 tokenizer.ggml.tokens,会话模板如 tokenizer.chat_template,都塞在这一段;③ 张量信息逐个登记每个权重张量的名字、形状、类型,以及它在文件里的偏移;④ 最后才是真正的 张量数据——一块块权重数值,位置正由上面那些偏移指过去。

为什么这么设计:全部塞进一个文件,加载时直接 mmap 进内存、按偏移随用随取,不必先解压或整体拷贝(CPU 推理时近乎零拷贝;用 GPU 后端则权重还会再拷进显存)。超参数与词表都自带,引擎读完头部就知道"这是什么模型、该怎么搭计算图",免配置文件、免 Python。把元数据排在权重前面也有讲究:引擎先读一小段头部把结构看清楚,再决定怎么映射后面那一大坨权重数据。还有个额外好处:因为是只读映射,多个进程能共享同一份权重内存,同机起多个实例时省内存又省加载时间。

源码:读写与解析在 ggml/src/gguf.cppgguf_kv / gguf_tensor_info 等结构);元数据键名的常量集中定义在 gguf-py/gguf/constants.py;把这些元数据接到 llama 模型上、按 key 取超参的,是 src/llama-model-loader.cpp

替代:更早的 GGML / GGJT 等老格式也干过同样的活,但字段零散、版本兼容差,已被 GGUF 取代(仓库里还留着一个 convert_llama_ggml_to_gguf.py 专门把老格式转过来)。

2 为什么 ggml 是独立子项目? 点击展开

一句话:因为同一个引擎被多个项目复用——ggml 不只为 llama.cpp 服务,所以它被切成一个能单独存在的子项目。

例子:同作者的 whisper.cpp(语音转文字)等项目也直接拿 ggml 当计算引擎;它们和 llama.cpp 共享同一套张量、算子与后端代码,只是上层逻辑不同。

怎么保持同步:ggml 有自己独立的上游仓库ggml-org/ggml),llama.cpp 里的 ggml/ 其实是它的一份镜像;仓库自带的 scripts/sync-ggml.sh 就负责把上游最新代码同步过来。于是引擎在自己的仓库里演进,各使用方(llama.cpp、whisper.cpp)再各自拉取,谁都不绑死谁。

源码:ggml/ 自带完整的 include/src/,对外的张量 / 计算图 / 后端接口是独立的一套,不依赖 src/llama-* 里的任何东西——依赖是严格单向的:src/llama-* 里用 #include "ggml.h" 调引擎,反过来 ggml 从不 include llama 的任何头文件。

好处:引擎可以独立演进(加新算子、新后端不必动 llama),也便于嵌入到任何想要本地张量计算的程序里;llama 只是它众多使用者中的一个。

3 common/ 和 src/ 有啥区别? 点击展开

一句话:common/ 是各个可执行程序共用的胶水不是推理库本体;真正的推理逻辑住在 src/llama-*

它管什么:命令行参数解析、把 C API 的采样接口包成更顺手的封装、日志、下载模型、聊天模板拼接……这些是"把库变成一个能用的程序"要反复写的活,抽到 common/tools/ 里每个程序都能复用。

它不管什么:加载权重、搭计算图、KV cache、真正的采样算法——这些都在 src/llama-* 里,对外只通过 include/llama.h 暴露。换句话说,删掉 common/src/llama-* 推理库照样能编译、照样能用,只是你得自己手写一堆样板代码。

依赖方向:这条链是严格单向的——tools/common/common/ 透过 include/llama.hsrc/llama-*src/llama-* 再往下压到 ggml,即 tools -> common -> llama.h -> src/llama-* -> ggml,越往右越底层、从不回头反向依赖。认准这个方向,遇到任何一个符号都知道"该去哪一层找"。

源码:参数解析看 common/arg.cpp,其余通用工具看 common/common.cpp

4 新增一个模型支持,大概动哪几处? 点击展开

一句话:顺着这张地图,给 llama.cpp 加一个新模型结构,通常只在三处落笔——一处登记、一处搭图、一处转换。

① 登记架构:src/llama-arch 里把新架构注册进来,并声明它用到的各类张量名字(有哪几种权重、各自叫什么)。这一步相当于在引擎的"花名册"上添个新成员。

② 搭前向图:src/llama-graph 提供的构件之上,把模型一次前向要做的事拼成计算图——嵌入、各层的注意力与前馈、归一化、输出。复用现成的 build_attn / build_norm 等积木,往往不必从零写算子。

③ 写转换器:convert_hf_to_gguf.py 里为这个模型加一段转换逻辑,把 HuggingFace 的权重与超参,按上面登记的张量名写进 .gguf

这里只是预告:三步各自的细节,后面"模型加载""计算图"相关的课会专门展开;此刻只需记住——新增模型不是漫天改动,而是沿着登记 -> 搭图 -> 转换这条窄路走一遍。

为什么能这么省事?正是前面那套分层在兜底:算子、后端、KV cache、采样这些通用机制早已写好、且与具体模型无关,新模型只需描述"我的结构长什么样",把现成积木重新搭一遍即可,无需重造引擎。这就是"边界清晰"在扩展性上的回报——加模型是沿既有接缝填空,而非动土重建。

✅ 本课要点
  • 仓库 = ggml(引擎)+ src/llama-*(推理库)+ common(胶水)+ tools / examples(程序)+ gguf-py / convert_*(模型准备)。
  • 这些目录对回四层:后端 -> ggml -> src/llama-* -> tools,自底向上各管一段。
  • 一个模型的一生是条流水线:Python 准备 -> .gguf -> C++ 运行,边界就是那个单文件。
  • 读源码按目标选入口:想会用看 tools/examples/simple,懂推理看 src/llama-*,懂算子看 ggml/
  • 对外只有一个公共 C API:include/llama.h——整个项目的外部契约
  • ggml 是独立、可复用的引擎;common 是胶水、不是推理本体——两者都别和 src/llama-* 搞混。
💡 设计亮点
引擎与模型逻辑分层 + 单头文件公共 API + Python 准备 / C++ 运行解耦——于是 ggml 能独立演进、被 whisper.cpp 等项目复用,llama 轻量地嵌进来用,转换脚本也不会拖累运行时。一张清晰的目录地图背后,其实是一组刻意划好的边界:谁依赖谁、谁对外、谁只是胶水,全摆在明面上。

🧪 自测 · 想一想为什么这么设计

1. 整个项目对外的公共 C API 主要收在哪里?
  1. common/common.h
  2. include/llama.h
  3. src/llama.cpp
  4. ggml/include/ggml.h
看答案与解析 点击展开
答案:B。对外契约只有 include/llama.h(外加 llama-cpp.h 的 C++ 薄封装);src 与 ggml 是内部实现。
2. 把一个 HuggingFace 模型变成能被 llama.cpp 运行的文件,靠的是哪部分?
  1. tools/llama-quantize
  2. ggml 后端
  3. gguf-py/ + convert_*.py(Python 转换脚本)
  4. src/llama-model-loader
看答案与解析 点击展开
答案:C。转换在 Python 侧(gguf-py + convert_*.py)产出 .gguf;C++ 运行时只负责加载已是 GGUF 的文件。
3. common/ 在整个项目里扮演什么角色?
  1. ggml 的一部分,负责底层算子
  2. 推理库本体:模型加载、计算图、采样都在这里
  3. 一组 Python 转换脚本
  4. 各可执行程序共用的“胶水”(参数解析、采样封装、日志…),推理本体在 src/llama-*
看答案与解析 点击展开
答案:D。common 把各程序重复要写的胶水(arg、采样封装、日志…)抽出来给 tools/ 复用;真正的加载/计算图/采样在 src/llama-*,对外只经 include/llama.h。
💭 发散思考(没有标准答案,动手或动脑想想)
  • 如果要新增一个采样策略,你认为应该改哪个目录?为什么不是 ggml/?

Open llama.cpp for the first time and a few hundred files across a dozen directories can feel overwhelming - where do you even start? In fact it is cleanly layered, every directory with one job. This lesson hands you a "campus map": learn what the top-level directories do, map them back onto the four layers from the previous lesson, then watch how a model travels from training to running - which directories it passes through and where the boundary falls. Once you have read the map you will not get lost, whether you want to run a tool or dig into the source.

🔌 Analogy
Think of the whole repo as a factory campus, and the directories are the map: each workshop (directory) does exactly one job - some build the engine (ggml), some do the assembly (src/llama-*), some are the storefront (tools/), and some are the loading dock that brings raw material in (convert_*.py turns an outside model into a .gguf). Reading the map - and seeing how the workshops connect - beats diving head-first into one workshop.

Top-level directories at a glance

Standing at the repo root, do not rush to open files; first scan the main directories by "what does this directory actually do". The table below is the map's legend:

DirectoryRole
ggml/The in-house tensor engine: tensors - compute graph - ops - backend scheduling; a standalone sub-project (ships its own include/ and src/) - the lowest and "hardest" layer of the whole project
ggml/src/ggml-cpu - ggml-cuda - ggml-metal - ggml-vulkan ...The individual hardware backends that actually run the ops on CPU / GPU (plus hip / sycl / musa / opencl and a dozen more)
src/The llama inference library: assembles the engine into "session-level" inference - llama-model-loader - llama-graph - llama-kv-cache - llama-sampler - llama-vocab - llama-chat - llama-grammar - llama-quant ...
include/The public C API: llama.h (the only external contract); llama-cpp.h (a thin C++ wrapper + RAII)
common/Reusable helpers / glue: arg (argument parsing) - sampling (sampler wrapper) - chat - log - download - json-schema-to-grammar ...; for the programs, not the inference library itself
tools/The executable programs: llama-cli - llama-server - llama-quantize - llama-mtmd-cli (multimodal) - llama-perplexity - llama-bench ...
examples/Small example programs: simple demonstrates minimal inference in a couple hundred lines - the best "readable" entry point
gguf-py/The Python GGUF read/write library: the conversion scripts use it to write out a .gguf
convert_hf_to_gguf.py, etc.Python conversion scripts (4 convert_*.py: 3 converters + 1 tokenizer-hash updater; the main one is HuggingFace -> GGUF)
models/ - tests/ - docs/ - grammars/ - cmake/Model data / tests / docs / GBNF examples / build system

A simple rule of thumb: the lower you go, the "harder" it gets - ggml/ leans toward math and hardware, src/ toward model and session logic, tools/ and common/ toward "for people to use". The only thing ever exposed to the outside, start to finish, is the single public header include/llama.h.

Four blocks, each owning one slice - why split them

The table lists many directories, but there are really only four protagonists, each locked onto one job and never crossing into another's. Get these four boundaries straight and the rest of that directory table becomes mere detail:

This blockDoes exactly one thingRoughly lives in
EngineProvide compute: define tensors, organize ops into a compute graph, schedule to the backends that actually run itggml/
Inference libAssemble compute into a "session": load weights, build the graph, KV cache, sampling, tokenizing, chat templatessrc/llama-*
Programs / glueFor people to use: the CLI, the HTTP server, the quantizer, plus the shared bits that turn a library into a programtools/ - common/
Model prepBring outside models in: turn HuggingFace weights into a .ggufgguf-py/ - convert_*.py

Why split them so hard? Because the four jobs change at completely different rhythms: backend ops chase new hardware and instruction sets; inference logic evolves with new model architectures; CLI flags and server APIs come and go with user needs; conversion scripts track upstream model formats. Weld them together and touching one means worrying you broke another. Split apart, each can evolve, be tested, and be reused on its own - add a new backend to ggml without touching a line of src/llama-*; add a new model architecture without reaching down to the low-level ops. That is the most concrete payoff of "clean boundaries".

Why the public API is a single header

One deliberate design is worth singling out: of the hundreds of internal files, the only thing exposed to the outside is the single header include/llama.h. Keeping the outward opening this narrow buys three things: first, change the internals freely - as long as the function signatures in llama.h stay put, however src/llama-* refactors internally, swaps data structures, or tweaks algorithms, no outside user is disturbed; second, a stable external contract, so consumers upgrade with confidence instead of chasing internal details; third, because what is exposed is a C ABI, almost any language can bind to it - Python, Go, Rust, Node and more all call the engine through this C interface, which is exactly how the many community language bindings are built. Narrow the outward opening to one small neck, and the internals earn the freedom to be refactored at will.

One aside: right next to llama.h sits llama-cpp.h, a thin convenience wrapper for C++ users - it uses RAII (smart pointers) to free llama_model / llama_context automatically, sparing you the manual free. It does not widen the exposed surface; it only wraps the same C interface more ergonomically, so "only one external contract" still holds.

How they map onto the "four layers"

Mapping those directories back onto the four-layer structure from the previous lesson makes it click:

tools & appstools/ - examples/
tools/ (cli - server - quantize - mtmd ...) and examples/: the user-facing CLI, server and samples
inference libsrc/llama-*
src/llama-* plus the public header include/llama.h: loading - compute graph - KV cache - sampling - tokenizer - chat templates
engineggml
ggml/ (ggml.c - gguf.cpp - ggml-alloc - ggml-backend): tensors - compute graph - ops - scheduling - the GGUF format
backendsCPU - CUDA - Metal - Vulkan ...
ggml/src/ggml-cpu - ggml-cuda - ggml-metal - ggml-vulkan ...: actually run the ops on hardware

Besides this main trunk there are two side-paths: (1) model prep - gguf-py/ plus convert_*.py (Python) turn a HuggingFace model into a .gguf file fed to the engine; (2) support - common/ (the glue that turns the library into programs) plus tests/ - docs/ - cmake/.

Why call them "side-paths"? Because an inference request only travels up and down the four-layer trunk - it never runs into these two. Model prep is done once, before you run (it emits the .gguf and steps aside), while support sits around the trunk like scaffolding: common/ spares programs from boilerplate, and tests/ - docs/ - cmake/ handle testing, docs, and the build. Tell apart "what the runtime passes through" from "what runs before or around it" and you won't mistake the scaffolding for a load-bearing wall when reading the source.

How a model travels from training to running

String the directories together and a model's "life" is really a straight pipeline: prepared in Python on the left, run in C++ on the right, handed over through one file in the middle. Walk it once and you will see which station each directory occupies along the whole chain:

HF / PyTorch model
safetensors weights
->
convert_hf_to_gguf.py
Python - gguf-py
->
model.gguf
single file - weights + metadata
->
llama_model_load_from_file
C++ runtime
->
emit text
llama-cli / server

The left half is Python (prepare), the right half is C++ (run), and the boundary between them is exactly that .gguf file in the middle.

That boundary matters: the conversion script runs once during prep, emits the .gguf, and then bows out; the runtime never touches Python and knows only this one file. So the same .gguf can feed llama-cli, llama-server, or examples/simple alike - they share the same loading and inference code.

Put this pipeline onto real commands and the common case is the three steps "convert -> quantize -> run". The block below is the minimal runnable skeleton - Python prepares on the left, C++ runs on the right, and the handover is still that .gguf in the middle:

# the full pipeline, from prep to run
# 1) convert: HuggingFace model -> GGUF (Python; --outfile sets the name)
python convert_hf_to_gguf.py ./my-model --outfile my-model.gguf   # 16-bit float (auto by default)
# 2) quantize (optional, to shrink)
llama-quantize my-model.gguf my-model-Q4.gguf Q4_0
# 3) run (C++ side)
llama-cli -m my-model-Q4.gguf -p "Hello"

The three steps land in three directories: step one runs in gguf-py/ + convert_*.py (Python) and spits out an FP16 .gguf; step two llama-quantize (from tools/) compresses it into a low-bit version like Q4_0, shrinking it to about a quarter of the size; step three llama-cli loads the quantized file and actually "emits text". Two things to remember: quantization is optional - if you don't care about size, just run the .gguf from step one; and steps two and three never touch Python, knowing only that one file - which is exactly what "the boundary is the .gguf" looks like once it lands on real commands.

What does convert_hf_to_gguf.py actually do in that step? It reads the config.json (hyper-parameters), the tokenizer, and the safetensors (weights) from the HuggingFace directory, renames tensors per the model architecture (transposing where needed), and writes it all out as one .gguf together with the metadata. In other words, this "loading dock" translates the outside world's motley models into the single format the engine recognizes - after which the C++ side need never care what they originally looked like.

While we are here, build some size intuition: the FP16 file from step one is about 14 GB for a 7B model; after Q4_0 in step two it drops to about 4 GB - the same command chain turns a model that "used to need a GPU" into a file that "fits in ordinary RAM". This echoes the "heavy to light" pipeline from the previous lesson, only this time landed on real commands.

Want to read the source - where to enter

Want to read the source but not sure where to start? Rather than chewing from the first file to the last, decide your goal first, then pick the matching entry point and drill down. Three common goals map to three entries:

to use ittools/ - examples/simple
First get llama-cli/llama-server running, then read the minimal call in examples/simple
to grok inferencesrc/llama-*
Follow the main line: llama-model-loader -> llama-graph -> llama-kv-cache -> llama-sampler
to grok the opsggml/
Into the engine: ggml.c and the ggml-* backends - how tensors/ops/scheduling are implemented

If you want just one place to start, examples/simple is the top pick: in roughly two hundred lines it runs the whole main line - "load model -> tokenize -> decode loop -> sample -> output" - with no server or multimodal side-branches to distract you. Read it top to bottom once, then map each call back onto the four layers above - does this function belong to the inference lib or the engine, which interface in llama.h does it go through - and the whole map goes from "seen" to "walked".

Whichever path you take, remember there is only one public contract, include/llama.h: when you cannot tell which part owns some capability, go back to this header and see which functions it exposes to the outside. Find the entry first, then drill down layer by layer - far more efficient than flipping through files at random.

Go deeper (optional)

Four questions below - open them if you want to see the whole map clearly; skip them if you just want the main trunk.

1 What is actually inside a GGUF file? click to expand

Example: a .gguf is roughly four sections end to end, packed in order inside one file:

GGUF file layout (single file, laid out in order)
magic + versionmetadata KV (hparams / vocab / chat template)tensor info (name / shape / type / offset)tensor data (weight blocks)
On load, the offsets in "tensor info" point mmap at the matching "tensor data" blocks - read on demand, no full copy.

Section by section: (1) the header is a four-byte magic "GGUF" plus a version number (currently 3), followed by "how many tensors, how many metadata entries"; (2) the metadata KV is a long list of key-value pairs - hyper-parameters like <arch>.block_count (layer count) and <arch>.embedding_length (hidden size), the vocab like tokenizer.ggml.tokens, the chat template like tokenizer.chat_template, all sit here; (3) the tensor info records each weight tensor's name, shape, type, and its offset in the file; (4) only last comes the actual tensor data - block after block of weight values, located exactly by those offsets.

Why this design: packing everything into one file means loading just mmaps it into memory and reads on demand by offset - no unpacking or whole-file copy first (near-zero-copy for CPU inference; with a GPU backend the weights are then copied into VRAM). The hyper-parameters and vocab are built in, so once the engine reads the header it knows "what model this is and how to build the compute graph", with no config files and no Python. Putting metadata before the weights is deliberate too: the engine reads a small header first to understand the structure, then decides how to map the big blob of weight data that follows. A bonus: because the mapping is read-only, multiple processes can share the same weight memory, saving RAM and load time when you run several instances on one machine.

Source: reading/parsing lives in ggml/src/gguf.cpp (the gguf_kv / gguf_tensor_info structs); the metadata key-name constants are defined in gguf-py/gguf/constants.py; wiring that metadata onto a llama model and fetching hyper-parameters by key is src/llama-model-loader.cpp.

Alternatives: the earlier GGML / GGJT formats did the same job, but with scattered fields and poor version compatibility - now superseded by GGUF (the repo still keeps a convert_llama_ggml_to_gguf.py just to migrate the old format over).

2 Why is ggml a standalone sub-project? click to expand

In one line: because the same engine is reused by several projects - ggml does not serve only llama.cpp, so it is carved out as a sub-project that can stand on its own.

Example: same-author projects like whisper.cpp (speech-to-text) use ggml directly as their compute engine; they share the very same tensor, op, and backend code as llama.cpp, only the upper layer differs.

How they stay in sync: ggml has its own standalone upstream repo (ggml-org/ggml), and the ggml/ inside llama.cpp is really a mirror of it; the repo's own scripts/sync-ggml.sh is what syncs the latest upstream code over. So the engine evolves in its own repo and each consumer (llama.cpp, whisper.cpp) pulls it in separately - nobody is locked to anybody.

Source: ggml/ ships its own complete include/ and src/; its tensor / graph / backend interface is a self-contained set that depends on nothing in src/llama-* - the dependency is strictly one-way: src/llama-* does #include "ggml.h" to use the engine, while ggml never includes any llama header.

Benefit: the engine can evolve independently (new ops or backends without touching llama) and is easy to embed in any program that wants local tensor compute; llama is just one of its many users.

3 What is the difference between common/ and src/? click to expand

In one line: common/ is the glue shared by the executable programs - it is not the inference library itself; the real inference logic lives in src/llama-*.

What it handles: command-line argument parsing, wrapping the C API's sampler into something handier, logging, downloading models, assembling chat templates... the boilerplate every "turn the library into a usable program" needs, factored into common/ so each program in tools/ can reuse it.

What it does not: it does not load weights, build the compute graph, manage the KV cache, or implement the actual sampling algorithms - those all live in src/llama-* and are exposed only through include/llama.h. In other words, delete common/ and src/llama-* still compiles and still works; you would just hand-write a pile of boilerplate yourself.

Dependency direction: the chain is strictly one-way - tools/ use common/, common/ calls src/llama-* through include/llama.h, and src/llama-* presses down onto ggml, i.e. tools -> common -> llama.h -> src/llama-* -> ggml, lower the further right, never doubling back. Fix this direction in your head and any symbol tells you "which layer to look in".

Source: for argument parsing see common/arg.cpp; for the rest of the shared helpers see common/common.cpp.

4 Adding support for a new model - roughly where do you touch? click to expand

In one line: following this map, adding a new model architecture to llama.cpp usually means edits in just three places - one to register, one to build the graph, one to convert.

(1) Register the architecture: in src/llama-arch, register the new architecture and declare the tensor names it uses (which weights, and what each is called). This step is like adding a new member to the engine's "roster".

(2) Build the forward graph: on top of the building blocks src/llama-graph provides, assemble what one forward pass does into a compute graph - embeddings, each layer's attention and feed-forward, normalization, output. Reusing ready-made bricks like build_attn / build_norm usually means you don't write ops from scratch.

(3) Write the converter: in convert_hf_to_gguf.py, add a conversion path for the model, writing the HuggingFace weights and hyper-parameters into a .gguf under the tensor names registered above.

This is only a preview: the details of each step are expanded in the later "model loading" and "compute graph" lessons; for now just remember - adding a model is not a sprawling change but a walk down the narrow path register -> build graph -> convert.

Why so cheap? Exactly because the layering underneath has your back: the generic machinery - ops, backends, KV cache, sampling - is already written and model-agnostic; a new model only describes "what my structure looks like" and re-assembles existing bricks, with no need to rebuild the engine. That is the payoff of "clean boundaries" for extensibility - adding a model is filling in along existing seams, not breaking ground to rebuild.

✅ Key points
  • The repo = ggml (engine) + src/llama-* (inference lib) + common (glue) + tools / examples (programs) + gguf-py / convert_* (model prep).
  • Those directories map back onto the four layers: backend -> ggml -> src/llama-* -> tools, bottom-up, each owning one slice.
  • A model's life is a pipeline: Python prep -> .gguf -> C++ run; the single file is the boundary.
  • Read the source by goal: to use it look at tools/ and examples/simple, to grok inference src/llama-*, to grok the ops ggml/.
  • The only public C API is include/llama.h - the project's external contract.
  • ggml is an independent, reusable engine; common is glue, not the inference core - do not confuse either with src/llama-*.
💡 Design insight
Engine / model-logic layering + a single-header public API + Python-prep / C++-run decoupling - so ggml can evolve independently and be reused by projects like whisper.cpp, llama embeds lightly, and conversion never burdens the runtime. Behind one clean directory map sits a set of deliberately drawn boundaries: who depends on whom, who faces outward, who is just glue - all out in the open.

🧪 Self-test - think about the design

1. Where does the project's public C API mainly live?
  1. common/common.h
  2. include/llama.h
  3. src/llama.cpp
  4. ggml/include/ggml.h
Show answer & explanation click to expand
Answer: B. The public contract is just include/llama.h (plus the llama-cpp.h C++ wrapper); src and ggml are internal.
2. What turns a HuggingFace model into a file llama.cpp can run?
  1. tools/llama-quantize
  2. the ggml backends
  3. gguf-py/ + convert_*.py (Python conversion scripts)
  4. src/llama-model-loader
Show answer & explanation click to expand
Answer: C. Conversion happens in Python (gguf-py + convert_*.py) producing .gguf; the C++ runtime only loads already-GGUF files.
3. What role does common/ play in the project?
  1. Part of ggml, handling the low-level ops
  2. The inference core: model loading, the compute graph and sampling all live here
  3. A set of Python conversion scripts
  4. The shared 'glue' for the executables (arg parsing, sampler wrapper, logging...); the inference core is in src/llama-*
Show answer & explanation click to expand
Answer: D. common factors out the boilerplate the programs repeat (arg, sampler wrapper, logging...) for tools/ to reuse; the real loading/graph/sampling is in src/llama-*, exposed only via include/llama.h.
💭 Open questions (no single right answer - just think or try)
  • If you were adding a new sampling strategy, which directory would you change - and why not ggml/?