数据截至 (上游 commit d7a2074112d2)
llama.cpp — 架构与原理
30 秒导读: llama.cpp 是用纯 C/C++、零第三方依赖实现的大模型本地推理引擎(README.md:54-58)。你给它一个 GGUF 模型文件,它在笔记本 CPU、手机、或 GPU 上跑出 token——不用装 Python、不用 CUDA 工具链。它做到了两件别人没同时做到的事:把量化做到 2~6 bit 还能用(自研的 K-quants 体系),以及一套代码跑遍几乎所有硬件(ggml 后端抽象)。今天 Hugging Face 上的本地模型几乎都以它定义的 GGUF 格式发布。
1. 这是什么(零基础也能懂)
一句话定义
llama.cpp 是一个把训练好的大模型权重文件,变成一段可在普通硬件上运行的 C 程序的推理引擎:加载模型 → 接收文本 → 逐个 token 地生成回复。
它要解决谁的什么问题
设想你想在自己电脑上跑一个 8B 模型:
- 原始 FP16 权重约 16 GB,笔记本内存放不下,放下去也算不动。
- 装 PyTorch + CUDA 环境动辄几个 GB 依赖,还经常版本打架。
- 你只想要一个东西:双击就能跑的程序。
llama.cpp 的回答是:把权重量化成 4 bit(16 GB → ~4.5 GB),用 C 写死全部计算,编译成一个可执行文件。它最初只是 Georgi Gerganov 为了在 MacBook 上跑 LLaMA 写的黑客项目,现在长成了本地推理的事实标准——Ollama、LM Studio 等产品的推理内核都是它。
它能做什么
| 能力 | 具体支持 |
|---|---|
| 推理 | LLM 文本生成、VLM 多模态、embedding、reranking |
| 量化 | 2~8 bit 的 K-quants / I-quants 体系,离线量化工具 llama-quantize |
| 硬件后端 | CPU(x86 AVX/AMX、ARM NEON、RISC-V、POWER、s390x)、CUDA、Metal、Vulkan、SYCL、OpenCL、WebGPU、CANN、RPC 远程(ggml/src/ggml-*/ 一目录一后端) |
| 服务 | llama-server:OpenAI 兼容 HTTP API + 内置 Web UI + 连续批处理 |
| 模型生态 | LLaMA、Qwen、DeepSeek、Mistral、Gemma 等上百种架构(src/models/ 一个架构一个文件) |
用起来什么样
最小形态就是一条命令——模型直接从 Hugging Face 拉下来跑(README.md:33-36):
# 下载并对话
llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF
# 起一个 OpenAI 兼容的 API server
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF
给库用户(C API)的最小骨架,include/llama.h 里有完整注释版示例(include/llama.h:1231-1252):
// 示意,非源码 —— 展示 API 的五个动作
struct llama_model * model = llama_model_load_from_file("model.gguf", mparams);
struct llama_context * ctx = llama_init_from_model(model, cparams);
llama_tokenize(vocab, prompt, ...); // 文本 → token
llama_decode(ctx, llama_batch_get_one(...)); // 前向算一遍,得到 logits
llama_token id = llama_sampler_sample(smpl, ctx, -1); // 采样出下一个 token
一句话直觉
把 llama.cpp 想成一台「自己造机床的修理铺」。 别人用现成的机床(PyTorch)加工零件,它连机床都是自己造的(ggml 张量库);零件(权重)先压缩成特制包装(GGUF + 量化),送到哪台设备(CPU/GPU/手机)都有对应的车床(后端)能直接加工——全程不需要外部供应商。
2. 顶层全景(它大概怎么转)
2.1 分层结构
整套代码自底向上叠了四层,每一层都可以单独用:
┌─────────────────────────────────────────────────────────────┐
│ tools/ llama-cli · llama-server · llama-quantize … │ 可执行程序
├─────────────────────────────────────────────────────────────┤
│ common/ 参数解析 · 聊天模板 · 采样预设 · 下载 │ 应用支撑库
├─────────────────────────────────────────────────────────────┤
│ src/ (libllama) │ 推理库
│ 模型加载 → 按架构构图 → KV cache → decode → 采样 │
├─────────────────────────────────────────────────────────────┤
│ ggml/ │ 张量库(地基)
│ 张量/计算图 ── 后端抽象 ── CPU/CUDA/Metal/Vulkan… │
│ gguf.cpp(GGUF 读写) · ggml-quants.c(量化内核) │
└────────────────────── ───────────────────────────────────────┘
▲ 模型文件:GGUF(自描述二进制,见第 2 章)
怎么读这张图: 箭头方向是「依赖」——上层只能调用下层。ggml 不认识「transformer」,src 不认识「HTTP」;server 只是 libllama 的一个客户端。
2.2 部件职责
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
ggml_tensor / ggml_cgraph | 张量 = 类型+形状+stride+op+输入指针;图 = 节点列表 | ggml/include/ggml.h:673、ggml/src/ggml-impl.h:329 |
| 后端抽象(4 层接口) | reg(一类硬件)→ device(一块卡)→ backend(一条执行流)→ buffer(一块显存/内存) | ggml/src/ggml-backend-impl.h:17、:106、:161、:215 |
ggml_backend_sched | 调度器:把一张图按张量所在设备切成若干 split,逐个 backend 执行 | ggml/src/ggml-backend.cpp:1057、:1957 |
gguf_context | GGUF 文件解析:KV 元数据 + 张量目录 | ggml/src/gguf.cpp:217 |
| 量化内核 | block 量化的压缩/解压/整数点积,每种类型一对 quantize_row_* / ggml_vec_dot_* | ggml/src/ggml-quants.c、ggml/src/ggml-cpu/quants.c |
llama_model_loader | 打开 GGUF、mmap、把张量目录变成可寻址的权重表 | src/llama-model-loader.cpp:532 |
llama_model | 读 hparams/vocab/权重,按架构建层;build_graph 产出每张 batch 的计算图 | src/llama-model.cpp:2652 |
src/models/*.cpp | 每个模型架构一段「构图代码」(LLaMA 的在 src/models/llama.cpp) | src/models/llama.cpp:99 |
llama_kv_cache / llama_kv_cells | KV cache:按 cell 管理槽位、支持多序列共享与滑动窗口 | src/llama-kv-cache.cpp、src/llama-kv-cells.h:41 |
llama_context::decode | 一次前向:校验 batch → 切 ubatch → 逐段建图/计算 → 收 logits | src/llama-context.cpp:1643 |
llama_sampler_* | 采样器链:top-k/top-p/temp/penalty/grammar 逐个过滤候选 | src/llama-sampler.cpp |
server_context | HTTP 服务:任务队列 + slot 状态机 + 连续批处理 | tools/server/server-context.cpp:833、:2677 |
2.3 主线走一遍(一次生成)
从「用户在 CLI 敲下一句话」到「屏幕上多出一个字」,数据这样流:
文本 prompt
│ ① tokenize:BPE 切词(src/llama-vocab.cpp)
▼
token 序列 ──► ② llama_decode(prompt 批):预填充
│ 按架构建一张 ggml 图(几百个节点)
│ 调度器切图 → 各后端执行 → 写出 K/V 进 cache,出 logits
▼
logits ──► ③ 采样器链过滤(top-k → top-p → temp → …)→ 得到新 token
│
▼
④ 循环:把新 token 追加进 batch,再 decode(每步只算 1 个 token,
注意力靠 KV cache 看到全部历史)──► 直到 EOS / 长度上限
这条线的核心经济结构:第 ② 步是 GEMM(prompt 几百个 token 一起算,把权重摊薄),第 ④ 步是 GEMV(每步 1 个 token,瓶颈在把权重从内存搬一遍)——所以 llama.cpp 的全部性能故事几乎都围绕「怎么让权重读得更快」:量化就是压缩搬运量,mmap 就是交给 OS 按需 调页。
3. 阅读地图(建议顺序)
五章由浅入深。时间有限读 01 → 03 → 04 就能抓住 llama.cpp 的全部要害:图怎么算、权重怎么压、token 怎么生。
| 顺序 | 章节 | 讲什么 | 适合谁 |
|---|---|---|---|
| 1 | 01-ggml-tensor-library.md | ggml:静态图两段式执行、无 malloc 的内存观、后端四层接口、跨设备切图 | 所有人必读,这是整套栈的地基 |
| 2 | 02-gguf-format.md | GGUF:文件布局、自描述设计、mmap 加载 | 关心模型格式/分发的人 |
| 3 | 03-quantization.md | 量化数学:分组量化、K-quants 双层 scale、运行时 Q8 激活点积 | 想搞懂「4-bit 为什么能用」的人 |
| 4 | 04-inference-loop.md | llama 库:模型加载、构图、KV cache、decode、采样链 | 想读推理主循环源码的人 |
| 5 | 05-server-cli.md | server:slot 连续批处理、OpenAI API;CLI 为何内嵌 server | 做部署/服务化的人 |