本地大模型栈总览与阅读路线
「本系列第 1/17 章」
本系列共十七章,编号从 00 到 16。它不是四个仓库各自再写一遍目录,而是按「训练 → 转换 → 推理 → 检索增强」把一条可在本机复现的本地大模型栈串起来。本章先画边界,再给出阅读顺序;从下一章起进入 GGML 0.15.3 的张量与计算图。
一条栈,四段职责
本地栈不要理解成「把 Hugging Face、llama.cpp、LlamaIndex 塞进同一个进程」。四段产物不同、进程不同、出了问题的排查入口也不同:
- 训练:LLaMA-Factory
0.9.6.dev0在 Hugging Face 权重上做监督微调与偏好对齐,磁盘产物仍是 SafeTensors / PyTorch,不是 GGUF。 - 转换与量化:
convert_hf_to_gguf.py把 HF 模型写成 GGUF;llama-quantize再压到生产常用的Q4_K_M。IQ 系列必须带 imatrix,否则质量会塌。 - 推理:llama.cpp 用 GGML 建图并在 CPU / CUDA / Metal / Vulkan 上执行;对外由
llama-server提供 OpenAI 兼容 HTTP。 - RAG:LlamaIndex
0.14.23不读 GGUF,也不管理 KV cache。它只用OpenAILike/OpenAILikeEmbedding打到llama-server。
边界必须守住。LLaMA-Factory 不懂 GGUF 的 32 字节对齐,也不懂 vec_dot。GGML 不懂 chat template,也不决定采样策略。LlamaIndex 看不见量化 block,更不会去改 n_gpu_layers。日志里一旦同时出现「训练 loss」「GGUF magic」「retriever 空结果」,先判断卡在哪一段,再往下翻对应章节。
GGML 在栈中的位置
GGML 0.15.3 是 llama.cpp 的张量引擎,不是应用框架。库拆成三层链接:ggml-base 放张量、图、量化、GGUF、Backend 接口、调度器与分配器;ggml 负责 Backend 发现与注册;ggml-cpu、ggml-cuda、ggml-metal、ggml-vulkan 是各硬件实现。llama.cpp 的 libllama 同时链这三层。
每个 ggml_tensor 由 ne、nb、type、op、src 描述,维度上限 GGML_MAX_DIMS=4。nb 是字节 stride,因此 permute / view / transpose 可以不拷贝数据。ggml_mul_mat、ggml_rope、ggml_rms_norm 这类 API 只连边,不会立刻算矩阵。图要等 ggml_build_forward_expand 做拓扑排序并填 use_counts,再走三条执行路径之一:
| 路径 | API | 典型场景 |
|---|---|---|
| 纯 CPU | ggml_graph_compute |
示例与调试 |
| 单 Backend | ggml_backend_graph_compute |
单卡、单设备 |
| 多 Backend 调度 | ggml_backend_sched_graph_compute_async |
llama.cpp 生产路径 |
权重容器是 GGUF:magic 为 GGUF,当前 version 为 3,默认 alignment 为 32。llama.cpp 用 gguf_init_from_file 且 no_alloc=true:先解析 KV 与张量元数据,再用 Backend buffer 承接 data,而不是让 Context 自己 malloc 整份权重。
推理热路径是 Q4 权重 × Q8 激活 的 vec_dot,不是先反量化成 F32 再做通用 GEMM。生产量化优先 Q4_K_M。IQ 系列依赖 codebook,且 ggml_quantize_requires_imatrix 为真,没有校准矩阵就不要当成品。
内存分两套。Context 的 bump allocator 只管张量与图的元数据;真正的 data 由 gallocr 配合 dyn_tallocr 按拓扑分配。中间结果在 n_children==1 且 ggml_op_can_inplace 时可以原地复用。标了 GGML_TENSOR_FLAG_OUTPUT 的张量永不覆盖,否则 logits 会在下一次 decode 里被写坏。
Backend 是五层 vtable。静态注册顺序是 CUDA → Metal → … → CPU 最后,CPU 作兜底。调度器对图做三遍切分,n_copies=4 用来做跨设备 pipeline。CUDA 按形状走 mmvq / mmq / cublas;macOS 默认开 Metal;Vulkan 用 shader-gen 生成各量化类型的 SPIR-V。构建时最常碰到的开关是 GGML_CUDA、GGML_CPU_REPACK、GGML_CUDA_FA。
这些机制分别在第 2 到第 4 章展开。总览只要求你记住:算子 API 不计算,GGUF 只提供自描述权重,真正落地的是 Backend 与调度器。
训练侧停在 Hugging Face
LLaMA-Factory 0.9.6.dev0 默认走 v0:扁平 YAML 经 HfArgumentParser 变成 dataclass,再按 stage 选 workflow,最后落到 Hugging Face Trainer。它能做 SFT、DPO 一类对齐、导出 adapter 或合并后的 HF 权重,也能拉起自己的 chat / API / Board。那是另一条「HF 或 vLLM 推理」路径,不是本系列后续要接的 GGUF 路径。
要把训练结果送进 llama.cpp,必须承认一次格式断裂:HF 侧的 config.json、tokenizer 文件、SafeTensors 分片,与 GGUF 的 general.architecture、tokenizer.ggml.tokens、按 blk.{i} 命名的张量,不是同一套元数据。转换桥负责翻译,而不是「改个后缀」。
转换桥:HF 写成 GGUF,再压到 Q4_K_M
1 | python convert_hf_to_gguf.py /path/to/hf --outfile model-f16.gguf --outtype f16 |
第一行写出接近全精度的 GGUF,第二行用 ggml-quants.c 的参考实现做量化。量化工具不走 CUDA mmq,也不走 Metal shader。Q4_K_M 使用 K-quant 的 256 元素 super-block,体积与质量之间最稳,适合作为默认生产格式。若改 IQ,先用校准数据生成 imatrix,再交给 llama-quantize;否则 codebook 选点没有列重要性,生成会明显变差。
转换脚本还要写入架构 KV、词表和特殊 token。漏掉 bos / eos 或架构名写错,后面 llama.cpp 能打开文件,但建图会对不上层数或头数。分片 GGUF 用 split.count / split.no 描述,加载器会合成一张权重表。
推理侧:llama.cpp 建图,llama-server 对外
llama.cpp 读 GGUF,按架构建 ggml_mul_mat / ggml_rope / ggml_flash_attn_ext 等节点,管理 KV cache 与采样,经 ggml_backend_sched_graph_compute_async 执行。llama-server 把同一套能力暴露为 /v1/chat/completions 与 /v1/embeddings。上下文长度、GPU offload、并发 slot、chat template 都在这个进程里,不在 RAG 框架里。
因此「模型很慢」首先看 Backend 是否真的注册到 CUDA / Metal,以及调度器有没有把 MUL_MAT 切到 GPU;「接口 404」才去看 server 路由。不要在 LlamaIndex 里找 ggml_type。
应用侧:LlamaIndex 只认 HTTP
LlamaIndex 0.14.23 的规范接法是进程外 HTTP:生成模型一个 llama-server,embedding 模型另一个,LlamaIndex 用 OpenAILike 与 OpenAILikeEmbedding 访问。它负责切分文档、建索引、检索与响应合成。GGUF 路径、量化类型、n_copies 对它不可见。
不要把进程内的 LlamaCPP 绑定与这条 OpenAILike 路径混配。前者把推理塞进 Python 进程,后者把推理留给 C++ server。本系列后续只沿 HTTP 这条线讲 RAG,以免配置项对不上。
十七章怎么读
按依赖读,不要跳过 GGML 三章直接抄 server 命令。编号 00 到 16 对应阅读顺序:
| 编号 | 主题 | 读完应能回答 |
|---|---|---|
| 00 | 本栈总览 | 四段职责如何切开 |
| 01 | GGML 张量与惰性图 | 为何 mul_mat 当时不算数 |
| 02 | 内存、量化、GGUF | 权重如何进 buffer,Q4×Q8 如何算 |
| 03 | Backend 与调度 | 图如何切到 CUDA / Metal / CPU |
| 04 | llama.cpp 加载与建图 | GGUF 如何变成可执行图 |
| 05 | Decode 与 KV Cache | 逐 token 如何复用图 |
| 06 | Batch 与采样 | 连续批处理停在哪一层 |
| 07 | llama-server | OpenAI 兼容 HTTP 如何落地 |
| 08 | LLaMA-Factory 入口与配置 | v0 训练如何启动 |
| 09 | 数据、模板与模型 | 样本如何变成 HF batch |
| 10 | 训练与对齐流水线 | SFT / 偏好阶段如何走 |
| 11 | 导出与转换桥 | HF 如何变成 Q4_K_M |
| 12 | LlamaIndex 架构 | Document 如何变成 Node |
| 13 | 索引、检索与合成 | 查询如何拼出 prompt |
| 14 | OpenAILike 对接 | 如何接到 llama-server |
| 15 | 端到端联调 | 训练到问答的检查点 |
| 16 | 性能、硬件与排错 | 如何选 Backend、看峰值 |
建议先读 00 到 03,把「惰性图 + 量化权重 + 可插拔 Backend」变成默认心智模型;再读 04 到 07,看 llama.cpp 如何把图变成服务;然后 08 到 11 把训练产物接进 GGUF;最后 12 到 16 做成可查询系统。
后文只写源码里能对上的 API 与常量,不编造一层「更友好」的封装。遇到「这个函数会立刻算出结果」的直觉,先回到张量与计算图那一章。遇到 OOM 或 logits 被覆盖,先回到内存分配与 OUTPUT 标志。遇到「明明编译了 CUDA 却在 CPU 上算」,先回到注册顺序与三遍调度。
读系列时要带着的三张检查表
第一张表问数据格式。训练完成时磁盘上应是 HF 目录。转换完成后应能用文件头四个字节 GGUF 认出容器,version 为 3。量化完成后 general.file_type 应指向 Q4_K_M 或你明确选择的类型;若是 IQ,旁边必须有 imatrix 来源。RAG 联调时,LlamaIndex 进程里不应出现 GGUF 路径,只应出现 http://127.0.0.1:8080 这类 base URL。
第二张表问进程边界。LLaMA-Factory 的 Trainer、convert_hf_to_gguf 的 Python 解释器、llama-quantize 的 C++ 工具、llama-server、LlamaIndex 应用,默认是五个进程、五份日志。把它们焊进一个 notebook 单元格,出了错无法判断是建图失败还是检索为空。本系列允许你在同一台机器上跑完全流程,但不鼓励把五段合成一个「一键脚本」再去调试。
第三张表问计算发生的位置。训练算力在 PyTorch。量化算力在 ggml-quants.c 参考实现,通常是 CPU。推理算力在 GGML Backend:CPU 的 vec_dot、CUDA 的 mmvq/mmq/cublas、macOS 的 Metal、跨平台的 Vulkan shader。RAG 侧几乎不占模型算力,它只拼 prompt、打 HTTP、写向量库。显存被占满时,先看 llama-server 而不是 LlamaIndex。
把三张表当作阅读锚点,十七章就不会读成互不相干的手册。你随时可以停下来问:此刻改的是格式、进程,还是计算位置?答案会直接指向后面某一章,而不是让你回到七十篇旧笔记里翻文件名。
下一章进入 GGML 的张量模型、计算图与惰性执行。
正在加载留言…