跳到主要内容

数据截至 (上游 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:673ggml/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_contextGGUF 文件解析:KV 元数据 + 张量目录ggml/src/gguf.cpp:217
量化内核block 量化的压缩/解压/整数点积,每种类型一对 quantize_row_* / ggml_vec_dot_*ggml/src/ggml-quants.cggml/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_cellsKV cache:按 cell 管理槽位、支持多序列共享与滑动窗口src/llama-kv-cache.cppsrc/llama-kv-cells.h:41
llama_context::decode一次前向:校验 batch → 切 ubatch → 逐段建图/计算 → 收 logitssrc/llama-context.cpp:1643
llama_sampler_*采样器链:top-k/top-p/temp/penalty/grammar 逐个过滤候选src/llama-sampler.cpp
server_contextHTTP 服务:任务队列 + 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 怎么生。

顺序章节讲什么适合谁
101-ggml-tensor-library.mdggml:静态图两段式执行、无 malloc 的内存观、后端四层接口、跨设备切图所有人必读,这是整套栈的地基
202-gguf-format.mdGGUF:文件布局、自描述设计、mmap 加载关心模型格式/分发的人
303-quantization.md量化数学:分组量化、K-quants 双层 scale、运行时 Q8 激活点积想搞懂「4-bit 为什么能用」的人
404-inference-loop.mdllama 库:模型加载、构图、KV cache、decode、采样链想读推理主循环源码的人
505-server-cli.mdserver:slot 连续批处理、OpenAI API;CLI 为何内嵌 server做部署/服务化的人

4. 巧妙之处(可借鉴的技术)

每条先白话,再给锚点;细节分散在各章。

  1. 「建图不算、算不建图」的两段式。 所有 ggml_mul_mat 之类的调用只往图里挂节点,不做任何计算(官方注释示例,ggml/include/ggml.h:54-69)。图可以复用、可以跨设备切分、可以预留显存——推理热循环里零 malloc。这是 ggml 区别于「边写边算」框架的根本。
  2. 量化不是「先解压再算」,而是「压缩态直接算」。 4-bit 权重与即时量化的 8-bit 激活做整数点积,整数和乘上两个 scale 得结果(ggml/src/ggml-cpu/quants.c:225ggml_vec_dot_q4_0_q8_0_generic)。反量化开销被消掉,省下的内存带宽直接变成速度。
  3. KV cache 写入是图里的节点。 拷贝 K/V 进 cache 的 ggml_cpy 被挂进计算图(src/llama-graph.cpp:2824-2825),于是「写 cache」天然参与跨设备调度、和注意力算子融合,而不是图外的一段胶水代码。
  4. 后端是运行时 dlopen 的插件。 CPU 永远内置,CUDA/Metal/Vulkan 等编译成动态库,启动时扫描加载(ggml/src/ggml-backend-reg.cpp:562ggml_backend_load_all)——一个二进制分发包就能适配所有硬件。
  5. CLI 即 server。 新版 llama-cli 在本地随机端口起个内嵌 server,然后自己当 HTTP 客户端连上去(tools/cli/cli-context.cpp:118-130tools/cli/cli-server.h:31-56)——从此「命令行交互」和「API 服务」只有一份生成逻辑要维护。

5. 边界与局限

诚实地说,它的长处(单文件、零依赖、全硬件)也划出了边界:

  • 为「单请求/少并发」而生,不为高吞吐而生。 decode 阶段逐 token GEMV,server 虽有连续批处理,但调度和 kernel 优化不以数据中心级吞吐为目标——那是 vLLM/SGLang 的赛道。
  • 不做训练。 权重格式、量化、内核全按「前向推理」设计;想微调去看 unsloth 这类训练向库。
  • 量化掉精度是模型相关、任务相关的。 K-quants 在 2~3 bit 档的损失可能显著,官方自己的纪律也是「跑 llama-perplexity 实测,别拍脑袋」(源卡片亦如此警示)。
  • 新架构支持是体力活。 每出一个新模型架构,要在 src/models/ 手写一段构图代码 + 在 convert_hf_to_gguf.py 里写转换器——「支持多少模型」取决于社区搬砖速度。
  • GGUF 是「一个文件一种模型」。 张量名、元数据 key 由转换脚本约定,格式本身不约束语义;遇到非标转换的 GGUF,加载失败通常在张量名匹配阶段。

6. 横向对比

同书架上的推理/训练栈,取舍各不相同:

项目定位与 llama.cpp 的核心差异
vLLM数据中心级 LLM servingPagedAttention + 高并发调度,追 GPU 吞吐;绑死 PyTorch/CUDA 生态
SGLang高性能 serving + 结构化生成RadixAttention 前缀共享、Python 栈;同样面向 GPU 集群
llm.c纯 C/CUDA 的 GPT-2 训练极简、教学向;一个模型、训练而非推理
unsloth高效微调库手写 Triton kernel 加速训练;GGUF 只是它的导出格式之一

一句话:vLLM/SGLang 用 Python + GPU 集群换吞吐,llama.cpp 用 C + 量化换「哪儿都能跑」;llm.c 是它的「训练侧极简镜像」;unsloth 产出的模型常以 GGUF 落地——它们是互补而非替代。

7. 代码地图(入口级)

每章末尾有细粒度地图,这里只列从零开始读源码的五个入口:

主题文件路径符号名
张量与图的定义ggml/include/ggml.hggml_tensorggml_typeggml_init_params
图执行(CPU 线程池)ggml/src/ggml-cpu/ggml-cpu.cggml_graph_computeggml_compute_forward_mul_mat
后端调度与切图ggml/src/ggml-backend.cppggml_backend_sched_split_graphggml_backend_sched_graph_compute
C API 表面include/llama.hllama_model_load_from_filellama_init_from_modelllama_decodellama_sampler_sample
一次前向的实现src/llama-context.cppllama_context::decodellama_context::process_ubatch