模型篇|llama.cpp 推理引擎深度解读:从一人练手到 LLM 端侧的事实标准

标签 :llama.cpp ggml GGUF 本地推理 量化 llama-server

适用读者:想在笔记本/工作站/服务器上本地跑大模型的开发者;被"装 CUDA、配 Python、抢显存"劝退过的人;要在 llama.cpp 与 vLLM/Ollama 之间做选型决策的工程师与架构者。

阅读完你将学会:llama.cpp 三层结构(ggml / 引擎库 / 工具链)各管什么;GGUF 文件内部结构(魔数、元数据 KV、张量目录、对齐与 mmap)与它成为端侧事实标准的原因;它从哪些机制里抠出速度(量化、KV cache、Flash Attention、continuous batching、投机解码);用官方 quickstart 一条命令起本地 OpenAI 兼容服务;以及选型时它和 vLLM、MLX、Ollama 的分工边界。

本篇素材 :llama.cpp 官方仓库(ggml-org/llama.cpp,2026-10-21 浅克隆 master 全文核读:README、docs/、tools/ 各 README、app/、skills/、源码架构注册表),GitHub API 仓库元数据与 Releases 同日核实。本篇为源码与文档级调研,未做本机运行实测 ,性能相关结论均标注官方口径。姊妹课程:audio.cpp 音频推理引擎深度解读、量化权重详解与选型。


一、这是什么:LLM 推理的"原生层"

把时间拨回 2023 年 3 月。Meta 的 LLaMA 权重刚流出,想本地跑它的人只有一条路:Python + PyTorch + 一张吃下 13B 全精度模型的大显卡。2023-03-10,Georgi Gerganov 在 GitHub 创建了 llama.cpp 仓库(GitHub API created_at 核实),思路简单到激进:用纯 C/C++ 重新实现 LLaMA 推理,零依赖,CPU 也能跑 。途中它还经历过 2023 年春被 Meta DMCA 短暂下架、移除争议内容后恢复的插曲,没有停下。三年半后(2026-10-21 核实):131,887 star / 24,624 fork ,MIT 许可,master 当天仍在推送,夜间构建版本号已排到 b11786------一天能出好几个版本。

它现在的官方定位(README 原文):"enable LLM (and VLM) inference with minimal setup and state-of-the-art performance on a wide range of hardware - locally and in the cloud"(以最小配置、在广泛硬件上实现最先进性能的 LLM/VLM 本地与云端推理)。注意两个关键词:LLM 和 VLM------2026 年的它早已不只跑纯文本模型,图像、音频、视频输入都走同一条管线。

项目名片 内容(2026-10-21 核实)
仓库 ggml-org/llama.cpp,原作者 Georgi Gerganov(ggerganov),现由 ggml-org 组织维护
建仓时间 2023-03-10(GitHub API),约三年半
热度 131,887 star / 24,624 fork / 抓取当日仍在推送(GitHub API)
引擎许可证 MIT(第三方头文件库各自署名,见 README 致谢)
版本节奏 正式版走语义化 tag;GitHub Releases 上是夜间构建 (b 递增编号,2026-10-21 当天从 b11780 到 b11786 共 7 版,docs/release.md)
模型覆盖 源码架构注册表 src/llama-arch.h 登记 138 个 LLM_ARCH_* 架构标识 (本机 grep 实数);Hugging Face library=gguf 过滤器下"数千个"现成模型(docs/models.md 原话 thousands)
硬件后端 17 个(README 后端表:CUDA、Metal、Vulkan、SYCL、HIP、CANN、MUSA、Hexagon、RPC、WebGPU 等)
入口 llama 统一命令(llama cli / llama serve / 量化、评测等子命令,源码 app/llama.cpp)与传统 llama-cli / llama-server 等工具并存

和上一篇的关系 :audio.cpp 篇说 audio.cpp 迎来了"语音模型的 llama.cpp 时刻"。本篇就是那个被当作度量衡的项目本身------它把"一个权重文件 + 一个二进制 + 全平台后端"这套范式跑通了,GGUF、ggml、量化命名的解释权都在这里。两篇共用同一套概念,本篇讲完,audio.cpp 篇里的每个术语都有了出处。

本篇贯穿案例在此声明,后文每一节都回扣它 :"一条命令的本地 OpenAI 服务" ------用官方 README quickstart 原命令 llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF,从 Hugging Face 直接拉起一个 Qwen3.5-0.8B,先在命令行对话(llama cli),再起 OpenAI 兼容 API 和内置 WebUI,最后看它怎么被量化与调优。这条路径三料同源:README quickstart、tools/cli 与 tools/server 的官方文档、docs/models.md 的 -hf 说明。

二、核心机制·上:三层积木------ggml、引擎库、工具链

2.1 三层分工:llama.cpp 其实是"三层蛋糕"

打开仓库源码,第一眼要分清的是三层完全不同的东西。很多人把它们混称"llama.cpp",选型和排障时就会找错层。

层 是什么 在仓库哪里 贯穿案例里它是谁
ggml(张量库) 独立的 C 张量计算库:算子、内存管理、全部硬件后端 。以独立仓库 ggml-org/ggml 维护,在本仓 ggml/ 目录同步进树 ggml/ Qwen3.5 的矩阵乘法最终落在 Metal 或 CUDA 后端,由 ggml 执行
libllama(引擎库) 模型层业务:架构注册表(138 个 LLM_ARCH_*)、分词、KV cache、采样、量化编排,对外暴露 include/llama.h C API src/、include/ 把 GGUF 里的 Qwen3.5 张量搭成计算图、逐 token 生成
工具链(入口层) 面向人的二进制:llama(统一命令)、llama-server、llama-quantize、llama-bench、llama-gguf-split、imatrix、mtmd(多模态)等 app/、tools/、examples/ 你敲的 llama serve 与浏览器里打开的 WebUI

一句话口诀:"ggml 管算,libllama 管模型,工具管人。" 这个分层就是 audio.cpp 那篇"后端层 / 框架层 / 入口层"的同源结构------audio.cpp 借的正是 ggml 这一层。

顺带解开 ggml 这个名字:官方 README 的自述只有一句 "Tensor library for machine learning" (面向机器学习的张量库),其中 ML 即 machine learning;GG 通行的读法是取自作者 G eorgi G erganov 的名字首字母(AUTHORS 名单与原始仓库名 ggerganov/ggml 都指向他)。注意这四个字母有三重身份,语境错了意思就错:张量库 ggml (本节的主角)、被 GGUF 取代的旧权重格式 ggml (convert_llama_ggml_to_gguf.py 迁移的就是它,见 2.2)、GitHub 组织 ggml-org(llama.cpp、whisper.cpp、ggml 仓库现都挂其下);GGUF 的全称 "GGML Universal File" 里嵌的也是它。
#mermaid-svg-GANS7jBTQAei6IQv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GANS7jBTQAei6IQv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GANS7jBTQAei6IQv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GANS7jBTQAei6IQv .error-icon{fill:#552222;}#mermaid-svg-GANS7jBTQAei6IQv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GANS7jBTQAei6IQv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GANS7jBTQAei6IQv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GANS7jBTQAei6IQv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GANS7jBTQAei6IQv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GANS7jBTQAei6IQv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GANS7jBTQAei6IQv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GANS7jBTQAei6IQv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GANS7jBTQAei6IQv .marker.cross{stroke:#333333;}#mermaid-svg-GANS7jBTQAei6IQv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GANS7jBTQAei6IQv p{margin:0;}#mermaid-svg-GANS7jBTQAei6IQv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-GANS7jBTQAei6IQv .cluster-label text{fill:#333;}#mermaid-svg-GANS7jBTQAei6IQv .cluster-label span{color:#333;}#mermaid-svg-GANS7jBTQAei6IQv .cluster-label span p{background-color:transparent;}#mermaid-svg-GANS7jBTQAei6IQv .label text,#mermaid-svg-GANS7jBTQAei6IQv span{fill:#333;color:#333;}#mermaid-svg-GANS7jBTQAei6IQv .node rect,#mermaid-svg-GANS7jBTQAei6IQv .node circle,#mermaid-svg-GANS7jBTQAei6IQv .node ellipse,#mermaid-svg-GANS7jBTQAei6IQv .node polygon,#mermaid-svg-GANS7jBTQAei6IQv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GANS7jBTQAei6IQv .rough-node .label text,#mermaid-svg-GANS7jBTQAei6IQv .node .label text,#mermaid-svg-GANS7jBTQAei6IQv .image-shape .label,#mermaid-svg-GANS7jBTQAei6IQv .icon-shape .label{text-anchor:middle;}#mermaid-svg-GANS7jBTQAei6IQv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GANS7jBTQAei6IQv .rough-node .label,#mermaid-svg-GANS7jBTQAei6IQv .node .label,#mermaid-svg-GANS7jBTQAei6IQv .image-shape .label,#mermaid-svg-GANS7jBTQAei6IQv .icon-shape .label{text-align:center;}#mermaid-svg-GANS7jBTQAei6IQv .node.clickable{cursor:pointer;}#mermaid-svg-GANS7jBTQAei6IQv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GANS7jBTQAei6IQv .arrowheadPath{fill:#333333;}#mermaid-svg-GANS7jBTQAei6IQv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GANS7jBTQAei6IQv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GANS7jBTQAei6IQv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GANS7jBTQAei6IQv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GANS7jBTQAei6IQv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GANS7jBTQAei6IQv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GANS7jBTQAei6IQv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GANS7jBTQAei6IQv .cluster text{fill:#333;}#mermaid-svg-GANS7jBTQAei6IQv .cluster span{color:#333;}#mermaid-svg-GANS7jBTQAei6IQv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GANS7jBTQAei6IQv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GANS7jBTQAei6IQv rect.text{fill:none;stroke-width:0;}#mermaid-svg-GANS7jBTQAei6IQv .icon-shape,#mermaid-svg-GANS7jBTQAei6IQv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GANS7jBTQAei6IQv .icon-shape p,#mermaid-svg-GANS7jBTQAei6IQv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GANS7jBTQAei6IQv .icon-shape .label rect,#mermaid-svg-GANS7jBTQAei6IQv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GANS7jBTQAei6IQv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GANS7jBTQAei6IQv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GANS7jBTQAei6IQv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} ggml 张量库(独立仓库同步进树)
libllama 引擎库
工具链(面向人)
17 个硬件后端
CUDA
Metal
Vulkan
SYCL / HIP / CANN / MUSA /

Hexagon / RPC / WebGPU / ...
llama 统一命令

cli / serve / quantize / bench / update
llama cli 对话
llama serve

OpenAI+Anthropic 兼容 API + WebUI
llama-quantize 量化
mtmd 多模态
架构注册表

138 个 LLM_ARCH_* 标识
KV cache 管理
采样 / 温度 / 语法约束
量化编排
算子:matmul / attention / 卷积...
后端调度

图里有一条定性结论 要显式说破:后端都挂在 ggml 一层,libllama 与工具不直接写 CUDA/Metal 代码。这就是"加一种新显卡只需要改 ggml"的架构原因,也是 17 个后端能养得起的根本。

举个例子 :贯穿案例里 llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF 敲下去------工具层的 llama 入口把 serve 子命令分发给服务器实现;引擎库从 HF 下载 GGUF、读出架构元数据、在注册表里找到 Qwen 架构的搭建方式,建好 KV cache;每个解码步的计算图交给 ggml,ggml 在你的设备上(Mac 走 Metal、N 卡走 CUDA、无卡走 CPU)挑后端执行。

2.2 GGUF:把权重、分词器、说明书装进一个文件

GGUF (仓库自带 Python 包 gguf 的 README 写作 GGML Universal File ,社区流传的其它全称展开以仓库自述为准)是 llama.cpp 定义的二进制容器:模型权重、分词器、聊天模板、超参数元数据打包成单个文件 。它不是项目第一天的格式------2023 年 8 月,贡献者 klosax 的 PR #2398 用 GGUF 替换了最初基于 ggml 的旧文件布局(格式规范出自 ggml 仓库的 PR #302),此后再未更换;格式的稳定性本身就是生态信心的来源。

文件内部长什么样 ?仓库 gguf-py/gguf/gguf_reader.py 的解析顺序就是权威答案,一个 GGUF 文件从头到尾是这几段:

段(按文件偏移顺序) 内容 关键细节(源码核实)
魔数 4 字节 0x46554747,即 ASCII "GGUF" 不匹配直接报 "GGUF magic invalid"
版本号 uint32,当前规范 v3 读取器兼容 v2+;版本字段还能探测大小端(反着读会命中字节序检查,自动切换)
张量数 + 键值数 两个 uint64 各自上限 2^30(1 GiB 个元素)防损坏文件撑爆内存
元数据键值区 N 条强类型 KV:uint8/int8/.../float64/bool/string/array 模型的"说明书"全在这里:general.architecture(架构标识,引擎据此路由到 138 个家族实现)、分词器配置、层数/头数等超参
张量信息区 M 条张量目录:名字、形状、各自的数据类型、数据偏移 类型可以是 F32/BF16,也可以是 Q8_0/Q4_K------"GGUF 不等于量化"在格式层的证据
对齐填充 补齐到对齐边界 默认 32 字节 ,可被元数据键 general.alignment 覆盖(读取器显式处理这个键)
张量数据区 紧随其后的裸数据块 按上面目录的偏移直接定位
为什么大模型文件能"秒开":mmap 的物理层拆解

先说 mmap (memory map,内存映射)是什么:一个系统调用,把文件的一段"铺"进进程的虚拟地址空间,之后程序把文件当内存数组读,页调度全部交给操作系统------真正被访问到的那一页,才从磁盘调入物理内存(demand paging,按需调页)。对照旧做法就看清差别了:传统加载是"打开→把几 GB 全量读进堆内存",启动时间约等于磁盘吞吐;mmap 加载只做"建立映射 + 读目录",秒级返回。

一条 GGUF 从打开到跑稳,物理上发生这五步:

步 发生什么 谁在做 贯穿案例里你看到的现象
1 建立映射 mmap 系统调用返回基址指针,操作系统只登记"这段地址对应这个文件",不搬任何数据 进程→内核 llama serve 启动日志立刻就绪
2 读目录 解析魔数、版本、元数据 KV、张量信息区------只有文件头部几百 KB 真正被读 进程 知道每个张量在文件里的偏移
3 首次访问 前向计算碰到某层权重 → CPU 访问的地址还没映射到物理内存 → 缺页中断 → 内核把对应页(常见 4KB,Apple Silicon 上 16KB)从磁盘调入 page cache 内核 第一条消息明显偏慢:逐页调入
4 稳态 常访问的权重页驻留物理内存,不再缺页 内核(LRU 淘汰) 后续 token 速度稳定
5 内存吃紧 内核可把冷页换出,下次访问再调回------换页而非崩溃 内核 表现为偶发卡顿,而不是 OOM 退出

为什么 GGUF 的布局是 mmap 的好搭档:张量信息区自带每个张量的 data_offset,读取器拿"基址 + 偏移"就是张量的裸视图,零拷贝、零重排 (Python 侧的 numpy.memmap 干的就是这件事,gguf_reader.py 原文);32 字节对齐让各段起始位置与内存页边界相处友好。

但要把话讲全,防止一个新误解:稠密 LLM 推理每个 token 都要扫全部权重 ,所以稳态下绝大部分页终究会被调入物理内存------mmap 不是"更省内存"的魔法。它真正买到的是三样东西:启动秒回 (目录先行)、内存不足时的优雅降级 (缺页换页代替直接失败)、多进程共享同一份内核页缓存(两个进程跑同一个 GGUF,物理内存里权重只存一份)。

驻留策略有两个配套旋钮(tools/server 参数表原文口径):

旋钮 档位 行为与适用
--load-mode auto(默认) mmap;设备不支持时自动退回普通加载
mmap 显式映射(文档提醒:禁用 mmap 时加载更慢,但不用 mlock 时可能减少页被换出)
mlock / mmap+mlock 锁页:强制权重常驻物理内存,禁止被换出或内存压缩------macOS 的内存压缩尤其值得锁,代价是内存账单一次性拉满
dio DirectIO,绕过 page cache 直读
--lazy-mode on / auto(默认)/ off 比"页按需"更细一档的"行按需 ":per-layer embeddings 这类巨型张量按行从磁盘现读,不常驻;auto 档只对 大于 4 GiB 的张量启用

举个例子 :16GB 内存的 Mac 硬跑约 40GB 的 70B Q4 模型------整块读必然失败;mmap + 分层卸载(-ngl)+ lazy-mode 让它"能跑起来",代价是首次访问各层时逐层调入、速度明显掉。这正是 4.3 误区五"能跑 ≠ 跑得快"的物理来源。

一句话口诀:"目录先进内存,正文用到哪页读哪页;怕被换出就 mlock 钉住。"

想亲手看一个 GGUF 的内部,不用写代码:pip install gguf 装上官方包,gguf_dump.py 打印全部元数据与张量表,gguf_set_metadata.py 改单个键值,gguf_editor_gui.py 直接给你一个 Qt 可视化编辑器(gguf-py README 工具清单)。

三个常见误解用格式事实钉死:

误解 事实
"GGUF 就是量化模型" 错。GGUF 是容器,每个张量各自声明类型 ,F32 全精度与 Q4 量化可混装在同一文件(本仓库量化篇拆过 Q4_K_M 这类混合档的内部构成)
"GGUF 是 llama.cpp 专用" 不再是。Hugging Face 为它设了 library=gguf 官方过滤器,"数千个"模型可直接拉(docs/models.md);Ollama、audio.cpp 等引擎都认它
"任何 GGUF 都能喂" 基本是,但老格式 GGML 文件需要 convert_llama_ggml_to_gguf.py 转换;多模态模型另有 mmproj 投影文件

GGUF 是怎么变成"事实标准"的:发布方照着发、工具链照着认 。官方维护 ggml-org HF 命名空间直接分发打包好的 GGUF(贯穿案例的 ggml-org/Qwen3.5-0.8B-GGUF 就是一例);Hugging Face 上还有官方 Space「GGUF-my-repo」把任意模型仓库转成 GGUF 并量化,每 6 小时同步 llama.cpp main (tools/quantize/README.md);配套「GGUF-my-LoRA」「GGUF-editor」覆盖 LoRA 转换与元数据编辑。audio.cpp 则把这套容器思想又推进一步------往元数据里内嵌整个模型 spec (见姊妹篇 2.2),同一份"单文件自带说明书"哲学的不同深度。

-hf 参数把生态闭环收成一条命令:-hf <user>/<model>[:quant] 直接从 HF 拉模型(还能用 MODEL_ENDPOINT 环境变量指向任何兼容 HF API 的镜像站------对国内网络环境是关键细节)。

2.3 多模态:mmproj,把"眼睛和耳朵"单独打包

mmproj (m ultim odal proj ector,多模态投影器)不是独立项目,是 llama.cpp 里的一个文件角色 :mmproj-*.gguf 与主模型文件是一对搭档。多模态大模型天然是两截,GGUF 把两截拆成两个文件:

文件 装什么 干什么
model.gguf(主模型) LLM 权重、文本分词器 语言推理
mmproj-*.gguf 视觉编码器(CLIP/SigLIP 系 ViT)或音频编码器,加一个投影 MLP 把图像切块/音频编码成向量,再"投影"成与 LLM 词嵌入同维度的向量,作为一串图像 token 插进输入序列

投影 MLP 就是两者的"转接头"------没有它,感知编码器的输出维度对不上 LLM 的输入空间。独立成文件换来三个自由度:按需挂载 (--mmproj 指定、--no-mmproj 关掉多模态只跑文本,-hf 拉多模态模型时自动带上)、分开量化 (主模型与感知侧各选档位)、独立卸载 (投影器默认进 GPU,--no-mmproj-offload 可关)。

实现落在仓库的 libmtmd 库与 tools/mtmd/;目录里留着 clip.cpp、legacy-models/ 和 deprecation-warning.cpp------这套支持是从早期 LLaVA/CLIP 视觉路线一路泛化来的,后来补上音频(mtmd-audio.cpp)。llama cli 与 llama serve 直接集成,多模态输入走 OpenAI 兼容的 /chat/completions。当前能力覆盖图像、音频、视频 三类输入;官方预打包模型在 HF 的 ggml-org/multimodal-ggufs 合集:视觉有 Gemma 3/4、Qwen2/2.5-VL、Pixtral、InternVL、Llama 4 Scout、SmolVLM 等,音频有 Ultravox、Voxtral、Qwen3-ASR,另有 PaddleOCR-VL、GLM-OCR、Deepseek-OCR 等专用 OCR 系(docs/multimodal.md)。

回扣姊妹课 :量化篇里的 mmproj-Confucius4-R2T2-*.gguf(当时按"音频侧文件"讲,含音频编码器及投影)正是同一机制------视觉模型配视觉 mmproj,语音模型配音频 mmproj,套路是同一套。

一句话口诀:主模型管"想",mmproj 管"看和听";转接头单独打包,随用随挂。

2.4 后端矩阵:17 个后端与"分层卸载"

README 的后端表有 17 行,值得按"你手里有什么硬件"来读:

硬件 后端 备注
Apple Silicon Metal README 明说"Apple silicon is a first-class citizen"(一等公民),NEON + Accelerate + Metal 全套优化
NVIDIA GPU CUDA 自研 kernel;AMD 走 HIP、摩尔线程走 MUSA
Intel GPU SYCL;CPU/GPU/NPU 走 OpenVINO(进行中)
华为 NPU CANN(Ascend)
高通 Hexagon(Snapdragon)
任意 GPU Vulkan(跨厂商兜底)
浏览器/无卡 WebGPU、CPU(AVX/AVX2/AVX512/AMX、RISC-V RVV)
分布式 RPC(把张量算子发到远端机器)

比后端数量更重要的是混合推理 能力(README Description 原文功能点):模型比显存大时不必全进 GPU-------ngl 控制多少层放显存;MoE 模型可用 --cpu-moe 把专家权重留在内存(详解见下);2026 年的新参数 --fit(默认开启)会自动调整未显式指定的参数,把模型塞进设备内存并留出目标余量(server 参数表)。也就是说"我的卡跑不动这个模型"在 2026 年的 llama.cpp 里更多是"跑得多慢"的问题,而不是"能不能跑"的问题。

--cpu-moe 详解:专家留内存,拿速度换容量

--cpu-moe(参数表原文:keep all Mixture of Experts weights in the CPU)的意思是:MoE 模型的专家层 FFN 权重全部常驻 CPU 内存,不占 GPU 显存;显存只装注意力权重、共享/稠密层、embedding 与 KV cache。执行随之分工------注意力在 GPU 算,路由到的专家计算落在 CPU 上算。

为什么 MoE 特别值得这么拆 。MoE(Mixture of Experts,混合专家)的结构特点是"总参数巨大、每 token 只激活一小撮 ":每个 MoE 层有多组并行专家 FFN,路由器按输入挑 k 个参与,其余专家这个 token 用不到;但不同 token 路由到不同专家,所以全部专家权重都得常驻在某处------这正是显存压力的来源。算一笔示意账:

全进显存 --cpu-moe 后
模型设定 总参数 100B、每 token 激活约 4B,Q4 权重约 50--60GB 同左
24GB 显存 放不下,跑不起来 只装注意力+共享层(几个 GB),专家约 50GB 留在 64GB 内存
结论 容量硬门槛 从"放不下"变"跑得起来"

代价必须同时说清:每个 token 总要算几个专家,而专家在内存里------要么 CPU 自己算,要么经 PCIe 搬运,两者都比显存慢一个量级。这是典型的用速度换容量(4.3 误区五的场景)。

配套的三个精细旋钮(server 参数表原文):

参数 作用
-cmoe, --cpu-moe 全部专家留 CPU
-ncmoe, --n-cpu-moe N 只把前 N 层的专家留 CPU(部分卸载,微调配平显存)
--moe-cache-mib N 给留在 CPU 的专家开一块 GPU 缓存 ,把"热"专家暂存进显存(多卡按 --tensor-split 分摊)------容量与速度的折中

稠密模型有对应开关 -ncffn, --n-cpu-ffn N(前 N 层稠密 FFN 留 CPU)。把这组旋钮放回全图:-ngl 按层 切、--cpu-moe 按张量类别 切(更细)、mmap/lazy-mode 按页/行 读盘------三层旋钮可叠加,共同构成"消费级硬件优雅降级跑大模型"的完整答案。另外,Mac 统一内存下 RAM 即显存,这个参数意义不大;它的主战场是独立显卡 + 大 MoE 模型。

一句话口诀:"注意力上卡,专家进内存;热专家再借显存缓存一用。"

三、核心机制·下:快,是从哪几处抠出来的

llama.cpp 的性能不是一个优化,是一叠优化。按推理的时序拆开讲,每个机制都回答"是什么 → 贯穿案例里它干了什么 → 为什么有效"。

3.1 量化:把权重从 16 位压到 4 位上下

是什么 :训练后量化------把 F32/BF16 权重改写为低位整数表示。README 支持清单原话:1.5、2、3、4、5、6、8-bit 整数量化 。工作流是两步(tools/quantize/README.md):先 convert_hf_to_gguf.py 把 HF 格式转成高精度 GGUF,再 llama-quantize 压到目标档位(如示例命令的 Q4_K_M);用 imatrix(重要性矩阵)文件可以引导量化决策、降低精度损失,KLD/困惑度是官方给的损失度量口径。

贯穿案例 :-hf 拉下来的 Qwen3.5-0.8B-GGUF 已经是官方量化好的包;你也可以自己转------把 HF 上的原仓库转 BF16 GGUF,再量化到 Q4_K_M,全程不碰 Python 训练框架。

为什么有效 :权重内存近似随位数线性下降,4 位档约为 16 位的三到四分之一,端侧设备从"放不下"变"放得下";同时低位整数乘加在 CPU/GPU 上都更省带宽。代价与档位选择的完整决策树见量化篇,此处不重复展开。另外两处容易漏的量化点:KV cache 也能量化 (-ctk/-ctv 支持 q8_0、q4_0 等档,长上下文省一大块缓存内存),以及 audio.cpp 篇讲过的"混合精度打包"在这里同样成立。

3.2 KV cache:生成快不快,一半看缓存

先锚定角色 :KV cache 是 libllama 引擎层维护的状态,模型权重不知道它的存在。要理解它,得先看注意力为什么"天生要重算"。

概念:注意力的"问、钥匙、内容"与可缓存性

自回归生成第 N 个 token 时,模型要对前文所有 token 做注意力:每个 token 在每一层都有三样东西------Q (Query,"我想找什么")、K (Key,"我这里有什么标签")、V (Value,"我的实际内容"),三者都由该 token 自己的表示乘权重矩阵得出。新 token 的 Q 要和所有前文的 K 算相似度,再按权重混合它们的 V。

关键观察只有一个:因果注意力下,旧 token 的 K/V 不因新 token 到来而改变------token i 的 K/V 只由 token i 自己决定。这就是"可缓存性"的全部来源:前文的 K/V 算一次就够,之后每一步直接复用。没有这层缓存,生成第 100 个 token 要把前 99 个从头重新前向一遍,第 101 步再来一遍------每步都在为"过去"白打工。
#mermaid-svg-DwFn5HVbAgiJQmvE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DwFn5HVbAgiJQmvE .error-icon{fill:#552222;}#mermaid-svg-DwFn5HVbAgiJQmvE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DwFn5HVbAgiJQmvE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DwFn5HVbAgiJQmvE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DwFn5HVbAgiJQmvE .marker.cross{stroke:#333333;}#mermaid-svg-DwFn5HVbAgiJQmvE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DwFn5HVbAgiJQmvE p{margin:0;}#mermaid-svg-DwFn5HVbAgiJQmvE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster-label text{fill:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster-label span{color:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster-label span p{background-color:transparent;}#mermaid-svg-DwFn5HVbAgiJQmvE .label text,#mermaid-svg-DwFn5HVbAgiJQmvE span{fill:#333;color:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE .node rect,#mermaid-svg-DwFn5HVbAgiJQmvE .node circle,#mermaid-svg-DwFn5HVbAgiJQmvE .node ellipse,#mermaid-svg-DwFn5HVbAgiJQmvE .node polygon,#mermaid-svg-DwFn5HVbAgiJQmvE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DwFn5HVbAgiJQmvE .rough-node .label text,#mermaid-svg-DwFn5HVbAgiJQmvE .node .label text,#mermaid-svg-DwFn5HVbAgiJQmvE .image-shape .label,#mermaid-svg-DwFn5HVbAgiJQmvE .icon-shape .label{text-anchor:middle;}#mermaid-svg-DwFn5HVbAgiJQmvE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DwFn5HVbAgiJQmvE .rough-node .label,#mermaid-svg-DwFn5HVbAgiJQmvE .node .label,#mermaid-svg-DwFn5HVbAgiJQmvE .image-shape .label,#mermaid-svg-DwFn5HVbAgiJQmvE .icon-shape .label{text-align:center;}#mermaid-svg-DwFn5HVbAgiJQmvE .node.clickable{cursor:pointer;}#mermaid-svg-DwFn5HVbAgiJQmvE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DwFn5HVbAgiJQmvE .arrowheadPath{fill:#333333;}#mermaid-svg-DwFn5HVbAgiJQmvE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DwFn5HVbAgiJQmvE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DwFn5HVbAgiJQmvE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DwFn5HVbAgiJQmvE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DwFn5HVbAgiJQmvE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DwFn5HVbAgiJQmvE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster text{fill:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE .cluster span{color:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DwFn5HVbAgiJQmvE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DwFn5HVbAgiJQmvE rect.text{fill:none;stroke-width:0;}#mermaid-svg-DwFn5HVbAgiJQmvE .icon-shape,#mermaid-svg-DwFn5HVbAgiJQmvE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DwFn5HVbAgiJQmvE .icon-shape p,#mermaid-svg-DwFn5HVbAgiJQmvE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DwFn5HVbAgiJQmvE .icon-shape .label rect,#mermaid-svg-DwFn5HVbAgiJQmvE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DwFn5HVbAgiJQmvE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DwFn5HVbAgiJQmvE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DwFn5HVbAgiJQmvE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 有 KV cache:每步只算增量
要生成第 100 个 token
只算它自己的 1 份 Q/K/V
K/V 追加进缓存

Q 与缓存里全部 K 做注意力
第 101 步:又只算 1 份
没有 KV cache:每步重算整个前缀
要生成第 100 个 token
前 99 个 token 从头到尾

重新前向、重算 99 份 K/V
才得到第 100 个 token 的输出
第 101 步:整个再来一遍

原理:decode 的每一步,缓存是怎么运转的

新 token 在每一层做的事(逐层重复,直到最后一层):

步 每层发生什么 算多少
1 投影 新 token 的表示分别乘 Wq/Wk/Wv,得到它的 Q、K、V 只算这 1 个 token 的三份投影
2 追加 这对 K/V 写进该层缓存末尾 一次写
3 打分 新 Q 与缓存中全部 K 做点积,得注意力分数 读缓存,不重算
4 混合 softmax 后按分数加权缓存中全部 V,得本层输出 读缓存,不重算
5 上行 输出作为下一层的输入,回到第 1 步 逐层重复

#mermaid-svg-kVgCmeBRIrkUMXRD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kVgCmeBRIrkUMXRD .error-icon{fill:#552222;}#mermaid-svg-kVgCmeBRIrkUMXRD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kVgCmeBRIrkUMXRD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kVgCmeBRIrkUMXRD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kVgCmeBRIrkUMXRD .marker.cross{stroke:#333333;}#mermaid-svg-kVgCmeBRIrkUMXRD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kVgCmeBRIrkUMXRD p{margin:0;}#mermaid-svg-kVgCmeBRIrkUMXRD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster-label text{fill:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster-label span{color:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster-label span p{background-color:transparent;}#mermaid-svg-kVgCmeBRIrkUMXRD .label text,#mermaid-svg-kVgCmeBRIrkUMXRD span{fill:#333;color:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD .node rect,#mermaid-svg-kVgCmeBRIrkUMXRD .node circle,#mermaid-svg-kVgCmeBRIrkUMXRD .node ellipse,#mermaid-svg-kVgCmeBRIrkUMXRD .node polygon,#mermaid-svg-kVgCmeBRIrkUMXRD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kVgCmeBRIrkUMXRD .rough-node .label text,#mermaid-svg-kVgCmeBRIrkUMXRD .node .label text,#mermaid-svg-kVgCmeBRIrkUMXRD .image-shape .label,#mermaid-svg-kVgCmeBRIrkUMXRD .icon-shape .label{text-anchor:middle;}#mermaid-svg-kVgCmeBRIrkUMXRD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kVgCmeBRIrkUMXRD .rough-node .label,#mermaid-svg-kVgCmeBRIrkUMXRD .node .label,#mermaid-svg-kVgCmeBRIrkUMXRD .image-shape .label,#mermaid-svg-kVgCmeBRIrkUMXRD .icon-shape .label{text-align:center;}#mermaid-svg-kVgCmeBRIrkUMXRD .node.clickable{cursor:pointer;}#mermaid-svg-kVgCmeBRIrkUMXRD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kVgCmeBRIrkUMXRD .arrowheadPath{fill:#333333;}#mermaid-svg-kVgCmeBRIrkUMXRD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kVgCmeBRIrkUMXRD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kVgCmeBRIrkUMXRD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kVgCmeBRIrkUMXRD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kVgCmeBRIrkUMXRD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kVgCmeBRIrkUMXRD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster text{fill:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD .cluster span{color:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kVgCmeBRIrkUMXRD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kVgCmeBRIrkUMXRD rect.text{fill:none;stroke-width:0;}#mermaid-svg-kVgCmeBRIrkUMXRD .icon-shape,#mermaid-svg-kVgCmeBRIrkUMXRD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kVgCmeBRIrkUMXRD .icon-shape p,#mermaid-svg-kVgCmeBRIrkUMXRD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kVgCmeBRIrkUMXRD .icon-shape .label rect,#mermaid-svg-kVgCmeBRIrkUMXRD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kVgCmeBRIrkUMXRD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kVgCmeBRIrkUMXRD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kVgCmeBRIrkUMXRD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
新 token 进入第 1 层

(输入为上一层输出/embedding)
投影:Q、K、V = x·Wq / Wk / Wv

只算这 1 个 token
K/V 追加到该层缓存末尾
新 Q × 缓存中全部 K → 注意力分数
softmax 后加权缓存中全部 V
本层输出传入下一层
还有下一层?
最后一层出 logits

进入采样(3.6 之前的采样链)

对照没有缓存的情形,收益一句话:每步的投影计算量从"整个前缀"缩到"一个 token"------这是 decode 能快过实时流式输出的底层前提之一(另一头是 3.3 的带宽优化)。

内存账本与旋钮:缓存是长上下文的内存大头

KV cache 不是免费的:它随上下文长度线性增长,粗略公式为 缓存量 ≈ 2(K 和 V)× 层数 × 每层 K/V 头数 × 头维度 × 上下文长度 × 每元素字节数。长上下文会话里,缓存可以大到与权重同量级------所以 server 专门给了一组旋钮(参数表原文口径):

旋钮 默认 作用
-c, --ctx-size 0(用模型自带值) 缓存容量的上限,启动时按它预分配
-kvo, --kv-offload 开 缓存是否跟权重一起进 GPU
-ctk / -ctv f16 缓存本身的量化档:q8_0 约省一半、q4_0 更省(允许值含 f32/f16/bf16/q8_0/q4_0/q4_1/iq4_nl/q5_0/q5_1)
--swa-full 关 滑动窗口注意力(SWA)模型只缓存窗口内 K/V,此开关强制用全量缓存

这些旋钮串起全课两条线:3.4 说过的"并发上限=显存里的 KV 预算",省预算靠的正是 -ctk/-ctv;而 --kv-offload 与 2.4 的分层卸载是同一思想的两个落点。

举个例子 :贯穿案例的 WebUI 多轮对话"越聊越长却不变慢太多",就是因为旧轮次每个 token 的 K/V 行都还躺在缓存里,第 N 轮只增量计算新内容;直到会话触到 -c 上限才触发淘汰,那时模型"忘记"最早的对话,体感就是答非所问地跑偏。

一句话口诀:"旧 token 的钥匙和内容不会变------算一次,存起来,后面每步只添一行。"

3.3 Flash Attention:注意力算得更省

是什么 :把注意力的中间大矩阵(序列长度的平方)拆成分块计算,省内存访问。llama.cpp 里 -fa 参数 2026 年已是 auto 默认 (参数表:on/off/auto, default: auto)。

为什么有效:解码阶段的瓶颈常常不在浮点运算而在内存带宽,Flash Attention 减少的正是对大中间矩阵的读写。也正因为它改变计算顺序,KV cache 的 K/V 布局有配套要求------这是"开了 fa 某些缓存档位不兼容"这类问题的根源,排障时先查这一对组合。

3.4 Continuous batching:服务器吞吐的发动机

先锚定角色 :本节讲的是 llama-server 的调度层 ------批怎么拼、槽位怎么分,模型本身毫不知情,它只管"收到一个 batch 的张量、前向、吐 logits"。server README 功能清单的原话是 Parallel decoding with multi-user support / Continuous batching(多用户并行解码 + 连续批处理)。

概念:从"请求级"到"迭代级"的一步之差

GPU 做一次前向并不只能伺候一个序列------把多个请求的同一"步"拼成一个 batch 张量一起算,能把 kernel 启动和带宽开销摊薄,这是批处理(batching)的直觉。真正分出高下的是调度的粒度:

静态批(static batching,老做法) 连续批(continuous batching,llama-server)
调度粒度 一次请求:凑齐一批→整批进→整批出 一次迭代:每个 decode 步之间都是调度点
短请求先完成 陪跑空转------批没散,它的算力份额白白浪费 立刻收尾、释放槽位
新请求到达 排队等"整批结束" 有空位就插进来
一句话 批是一伙人凑一桌、吃完一起走 批像公交,每站可上可下

#mermaid-svg-Uf1Mndgx1U1nEkzu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Uf1Mndgx1U1nEkzu .error-icon{fill:#552222;}#mermaid-svg-Uf1Mndgx1U1nEkzu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Uf1Mndgx1U1nEkzu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .marker.cross{stroke:#333333;}#mermaid-svg-Uf1Mndgx1U1nEkzu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Uf1Mndgx1U1nEkzu p{margin:0;}#mermaid-svg-Uf1Mndgx1U1nEkzu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster-label text{fill:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster-label span{color:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster-label span p{background-color:transparent;}#mermaid-svg-Uf1Mndgx1U1nEkzu .label text,#mermaid-svg-Uf1Mndgx1U1nEkzu span{fill:#333;color:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .node rect,#mermaid-svg-Uf1Mndgx1U1nEkzu .node circle,#mermaid-svg-Uf1Mndgx1U1nEkzu .node ellipse,#mermaid-svg-Uf1Mndgx1U1nEkzu .node polygon,#mermaid-svg-Uf1Mndgx1U1nEkzu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .rough-node .label text,#mermaid-svg-Uf1Mndgx1U1nEkzu .node .label text,#mermaid-svg-Uf1Mndgx1U1nEkzu .image-shape .label,#mermaid-svg-Uf1Mndgx1U1nEkzu .icon-shape .label{text-anchor:middle;}#mermaid-svg-Uf1Mndgx1U1nEkzu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .rough-node .label,#mermaid-svg-Uf1Mndgx1U1nEkzu .node .label,#mermaid-svg-Uf1Mndgx1U1nEkzu .image-shape .label,#mermaid-svg-Uf1Mndgx1U1nEkzu .icon-shape .label{text-align:center;}#mermaid-svg-Uf1Mndgx1U1nEkzu .node.clickable{cursor:pointer;}#mermaid-svg-Uf1Mndgx1U1nEkzu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .arrowheadPath{fill:#333333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Uf1Mndgx1U1nEkzu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Uf1Mndgx1U1nEkzu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Uf1Mndgx1U1nEkzu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster text{fill:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu .cluster span{color:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Uf1Mndgx1U1nEkzu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Uf1Mndgx1U1nEkzu rect.text{fill:none;stroke-width:0;}#mermaid-svg-Uf1Mndgx1U1nEkzu .icon-shape,#mermaid-svg-Uf1Mndgx1U1nEkzu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Uf1Mndgx1U1nEkzu .icon-shape p,#mermaid-svg-Uf1Mndgx1U1nEkzu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Uf1Mndgx1U1nEkzu .icon-shape .label rect,#mermaid-svg-Uf1Mndgx1U1nEkzu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Uf1Mndgx1U1nEkzu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Uf1Mndgx1U1nEkzu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Uf1Mndgx1U1nEkzu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新:连续批(迭代级------随到随补)
下一迭代
每个 decode 迭代之间

都是调度点
A 生成完

立刻收尾并释放槽位
E 恰好入队

立刻插进来(先做 prefill)
B C E 同批各出 1 个 token
旧:静态批(请求级------等整轮)
凑齐一批请求

A B C D
整批一起跑
A 先生成完 → 陪跑空转

批没散,GPU 还在等最慢的 D
D 终于结束

整批才散
排队的新请求 E

此刻才被接入

一条定性结论值得单独成句:调度发生在 token 与 token 的缝隙里------这就是"iteration-level scheduling(迭代级调度)"的字面意思。

原理:迭代间隙里,调度器是怎么运转的

调度器在每两个 decode 迭代之间跑一遍下面的循环。仍用贯穿案例的人物:WebUI 里 A、B 两个标签页正在对话,C 的提问恰好此刻到达:

步 调度器做什么 例子说明(贯穿案例)
1 收尾检查 上一迭代中生成了结束符或达到 -n(n_predict)上限的请求,标记完成、流式收尾、释放槽位与其 KV cache A 的回答刚好生成完 → 槽位空出
2 补位 检查等待队列:槽位与上下文预算放得下就让新请求插入,先做整段 prompt 的 prefill C 的提问入队 → 立刻插进 A 空出的位
3 拼批 所有在役请求"当前步要算的 token"拼成一个 batch 张量,一次前向 B 与 C 同批,各推进 1 个 token
4 分发 按请求切开 logits,各自采样(回到 3.6 之前的采样链)、各自流式推送 B、C 的页面同时逐字出字,互不串线
5 循环 回到第 1 步,永不整批等待 A 走了,B、C 继续在新批里推进

画成流程图:
#mermaid-svg-LB7vBV7bsOb739yf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LB7vBV7bsOb739yf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LB7vBV7bsOb739yf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LB7vBV7bsOb739yf .error-icon{fill:#552222;}#mermaid-svg-LB7vBV7bsOb739yf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LB7vBV7bsOb739yf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LB7vBV7bsOb739yf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LB7vBV7bsOb739yf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LB7vBV7bsOb739yf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LB7vBV7bsOb739yf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LB7vBV7bsOb739yf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LB7vBV7bsOb739yf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LB7vBV7bsOb739yf .marker.cross{stroke:#333333;}#mermaid-svg-LB7vBV7bsOb739yf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LB7vBV7bsOb739yf p{margin:0;}#mermaid-svg-LB7vBV7bsOb739yf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LB7vBV7bsOb739yf .cluster-label text{fill:#333;}#mermaid-svg-LB7vBV7bsOb739yf .cluster-label span{color:#333;}#mermaid-svg-LB7vBV7bsOb739yf .cluster-label span p{background-color:transparent;}#mermaid-svg-LB7vBV7bsOb739yf .label text,#mermaid-svg-LB7vBV7bsOb739yf span{fill:#333;color:#333;}#mermaid-svg-LB7vBV7bsOb739yf .node rect,#mermaid-svg-LB7vBV7bsOb739yf .node circle,#mermaid-svg-LB7vBV7bsOb739yf .node ellipse,#mermaid-svg-LB7vBV7bsOb739yf .node polygon,#mermaid-svg-LB7vBV7bsOb739yf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LB7vBV7bsOb739yf .rough-node .label text,#mermaid-svg-LB7vBV7bsOb739yf .node .label text,#mermaid-svg-LB7vBV7bsOb739yf .image-shape .label,#mermaid-svg-LB7vBV7bsOb739yf .icon-shape .label{text-anchor:middle;}#mermaid-svg-LB7vBV7bsOb739yf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LB7vBV7bsOb739yf .rough-node .label,#mermaid-svg-LB7vBV7bsOb739yf .node .label,#mermaid-svg-LB7vBV7bsOb739yf .image-shape .label,#mermaid-svg-LB7vBV7bsOb739yf .icon-shape .label{text-align:center;}#mermaid-svg-LB7vBV7bsOb739yf .node.clickable{cursor:pointer;}#mermaid-svg-LB7vBV7bsOb739yf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LB7vBV7bsOb739yf .arrowheadPath{fill:#333333;}#mermaid-svg-LB7vBV7bsOb739yf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LB7vBV7bsOb739yf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LB7vBV7bsOb739yf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LB7vBV7bsOb739yf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LB7vBV7bsOb739yf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LB7vBV7bsOb739yf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LB7vBV7bsOb739yf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LB7vBV7bsOb739yf .cluster text{fill:#333;}#mermaid-svg-LB7vBV7bsOb739yf .cluster span{color:#333;}#mermaid-svg-LB7vBV7bsOb739yf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LB7vBV7bsOb739yf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LB7vBV7bsOb739yf rect.text{fill:none;stroke-width:0;}#mermaid-svg-LB7vBV7bsOb739yf .icon-shape,#mermaid-svg-LB7vBV7bsOb739yf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LB7vBV7bsOb739yf .icon-shape p,#mermaid-svg-LB7vBV7bsOb739yf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LB7vBV7bsOb739yf .icon-shape .label rect,#mermaid-svg-LB7vBV7bsOb739yf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LB7vBV7bsOb739yf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LB7vBV7bsOb739yf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LB7vBV7bsOb739yf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
是
否
进入下一迭代
迭代间隙:调度器开工
有请求完成?

结束符 / 达到 n_predict
流式收尾

释放槽位与 KV
队列里有请求?

槽位与上下文预算够?
新请求插入

先做整段 prompt 的 prefill
在役请求当前步拼成一个 batch

一次前向
按请求切开 logits

各自采样 · 各自流式推送

注意 prefill 与 decode 的"活"不一样重:新请求的 prompt 处理是一次吞下几百到几千 token 的计算密集大活 (3.6 口径表里的 pp),decode 是每步每请求 1 token 的带宽密集小活(tg)。连续批允许大活插在小活之间,这正是它吞吐优势的来源之一。

策略与边界:收益从哪来,上限在哪
  • 收益一(吞吐):单请求逐 token 生成吃不满 GPU(tg 是内存带宽型),多请求拼批把算力和带宽同时填满------同一块卡服务的并发请求数显著上升;
  • 收益二(延迟):新请求不再等"整批结束"才被服务,排队延迟直接砍掉------用户体感是"提问马上开始逐字出";
  • 上限在 KV 预算 :每个在役请求都要占一份 KV cache(回扣 3.2),并发路数的硬上限是"显存里能放下几份上下文缓存"------这也是 -ctk/-ctv 把 KV 量化到 q8_0/q4_0 的真正动机:省的就是这一份份预算;
  • 取舍:批越大、单流速度略降(每步要伺候的请求多了),吞吐与单流延迟是一对此消彼长的指标,服务端并发调优调的就是这个平衡点。

举个例子:贯穿案例里 A、B 两个标签页同时提问、各自独立逐字出字,A 先答完先走、C 随到随进------用户看到的是三个"互不干扰的对话",机器里其实是同一个批在不同请求间轮转推进。

一句话口诀:"批是逐 token 拼的,位是随到随补的;短的不陪跑,长的不堵门。"

3.5 投机解码:小模型起草,大模型验卷

是什么:speculative decoding(docs/speculative.md):让一个小 draft 模型先猜 K 个 token,大模型一次前向同时验证这 K 个猜测,对的直接采纳。猜得准就等于一次前向出了 K 个 token。

为什么有效:大模型逐 token 生成的瓶颈是"一次前向只出一个 token";并行验证把串行步骤摊薄。适合搭配同家族大小模型(官方文档给用法),对话场景加速明显,代价是多养一个小模型、且接收率依赖任务。
#mermaid-svg-Gmd1g0k4JYwrOk0P{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Gmd1g0k4JYwrOk0P .error-icon{fill:#552222;}#mermaid-svg-Gmd1g0k4JYwrOk0P .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Gmd1g0k4JYwrOk0P .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .marker.cross{stroke:#333333;}#mermaid-svg-Gmd1g0k4JYwrOk0P svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Gmd1g0k4JYwrOk0P p{margin:0;}#mermaid-svg-Gmd1g0k4JYwrOk0P .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster-label text{fill:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster-label span{color:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster-label span p{background-color:transparent;}#mermaid-svg-Gmd1g0k4JYwrOk0P .label text,#mermaid-svg-Gmd1g0k4JYwrOk0P span{fill:#333;color:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .node rect,#mermaid-svg-Gmd1g0k4JYwrOk0P .node circle,#mermaid-svg-Gmd1g0k4JYwrOk0P .node ellipse,#mermaid-svg-Gmd1g0k4JYwrOk0P .node polygon,#mermaid-svg-Gmd1g0k4JYwrOk0P .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .rough-node .label text,#mermaid-svg-Gmd1g0k4JYwrOk0P .node .label text,#mermaid-svg-Gmd1g0k4JYwrOk0P .image-shape .label,#mermaid-svg-Gmd1g0k4JYwrOk0P .icon-shape .label{text-anchor:middle;}#mermaid-svg-Gmd1g0k4JYwrOk0P .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .rough-node .label,#mermaid-svg-Gmd1g0k4JYwrOk0P .node .label,#mermaid-svg-Gmd1g0k4JYwrOk0P .image-shape .label,#mermaid-svg-Gmd1g0k4JYwrOk0P .icon-shape .label{text-align:center;}#mermaid-svg-Gmd1g0k4JYwrOk0P .node.clickable{cursor:pointer;}#mermaid-svg-Gmd1g0k4JYwrOk0P .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .arrowheadPath{fill:#333333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gmd1g0k4JYwrOk0P .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Gmd1g0k4JYwrOk0P .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gmd1g0k4JYwrOk0P .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster text{fill:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P .cluster span{color:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Gmd1g0k4JYwrOk0P .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Gmd1g0k4JYwrOk0P rect.text{fill:none;stroke-width:0;}#mermaid-svg-Gmd1g0k4JYwrOk0P .icon-shape,#mermaid-svg-Gmd1g0k4JYwrOk0P .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Gmd1g0k4JYwrOk0P .icon-shape p,#mermaid-svg-Gmd1g0k4JYwrOk0P .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Gmd1g0k4JYwrOk0P .icon-shape .label rect,#mermaid-svg-Gmd1g0k4JYwrOk0P .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Gmd1g0k4JYwrOk0P .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Gmd1g0k4JYwrOk0P .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Gmd1g0k4JYwrOk0P :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 未结束
结束
请求进入 llama-server
slot 调度

多请求进同一批
prefill:整段 prompt 一次前向

KV cache 写入
decode 循环:每步每请求 1 token

continuous batching 拼批
采样:温度 / top-k / top-p /

GBNF 语法 / JSON schema 约束
流式返回客户端

槽位让给下一个请求

3.6 读性能数字的口径纪律

官方基准工具是 llama-bench(tools/llama-bench)。它分两张口径,混用就是鸡同鸭讲:

口径 测什么 谁是瓶颈
pp(prompt processing) 吞整段 prompt 的速度(token/s) 计算密集,类似批量前向,吃满算力
tg(token generation) 逐 token 生成速度(token/s) 内存带宽密集,是"聊天流式输出快不快"的直接口径
  • ❌ 误区一:拿 tg 数字吹"吞吐高"------tg 是单流延迟口径,吞吐要看 server 并发批处理。
  • ❌ 误区二:跨硬件比数字不带后端与量化档------同一模型 Q4 与 BF16、Metal 与 CUDA 的差距可以大到没有可比性。
  • ❌ 误区三:把三年前社区帖子的跑分当现状------夜间构建一天 7 版(2026-10-21 releases 实况),优化在持续进。

四、动手:一条命令起一个本地 OpenAI 服务(贯穿案例走查)

4.1 完整上手路径

下表从零走到"本地 OpenAI 兼容服务 + WebUI",是贯穿案例的完整生命周期,每步给官方出处:

步 动作 官方依据 例子说明(贯穿案例)
1 装运行时 README quickstart:`curl -LsSf https://llama.app/install.sh sh`(macOS/Linux)或 PowerShell 一行;也可 llama.app、Docker、Releases 夜间预编译包、源码构建
2 命令行先聊一句 llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF 首次运行自动从 HF 拉模型;同一命令换 -hf 名字即换模型,本地缓存的模型可用 --cache-list 列出
3 起 OpenAI 兼容服务 llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF 对外暴露 /v1/chat/completions 等 OpenAI 路由,还有 Anthropic Messages API 兼容(server README 功能清单)
4 打开 WebUI server 内置 web UI README 截图即 WebUI 跑 Qwen 的实拍:会话、参数、多模态上传都在界面里
5 客户端接入 OpenAI SDK 把 base_url 指到本地 任何 OpenAI 兼容客户端/Agent 框架零改动接入本地模型
6 (可选)自制量化 convert_hf_to_gguf.py → llama-quantize,或 HF Space「GGUF-my-repo」免装转档 把想要的模型转 Q4_K_M;注意多模态模型的 mmproj 要单独转(tools/quantize README)
7 (可选)调优与观测 -fa(默认 auto)、-ctk/-ctv、--fit、--cpu-moe、监控端点 显存不够先什么都不加试试--------fit 默认开会自动收参数

两条核心命令原文(README quickstart):

bash 复制代码
# 下载并直接运行 Hugging Face 上的模型
llama cli -hf ggml-org/Qwen3.5-0.8B-GGUF

# 起 OpenAI 兼容 API 服务器
llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF

4.2 服务器能力与一条请求的生命周期

server README 的功能清单值得整表过一遍,因为它决定了"能不能拿它当生产服务"的答案:

能力 说明(server README)
OpenAI 兼容路由 chat completions、responses、embeddings
Anthropic 兼容 Messages API 形态的对话路由
并发 多用户并行解码 + continuous batching
多模态 图/音/视频输入走 OpenAI 兼容 API
结构化输出 schema 约束的 JSON 响应格式;GBNF 语法约束
Function calling 文档单列一页(docs/function-calling.md),"几乎任何模型"可用
其他 Reranking 端点(PR #9510)、助手消息预填充、监控端点、投机解码

一条请求的完整生命周期(承接 3.4 的机制):
ggml 后端(Metal/CUDA/CPU) slot 池(continuous batching) llama serve 客户端(OpenAI SDK / WebUI) ggml 后端(Metal/CUDA/CPU) slot 池(continuous batching) llama serve 客户端(OpenAI SDK / WebUI) #mermaid-svg-wgOhZ5yOSgIcEQHF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-wgOhZ5yOSgIcEQHF .error-icon{fill:#552222;}#mermaid-svg-wgOhZ5yOSgIcEQHF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wgOhZ5yOSgIcEQHF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wgOhZ5yOSgIcEQHF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wgOhZ5yOSgIcEQHF .marker.cross{stroke:#333333;}#mermaid-svg-wgOhZ5yOSgIcEQHF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wgOhZ5yOSgIcEQHF p{margin:0;}#mermaid-svg-wgOhZ5yOSgIcEQHF .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wgOhZ5yOSgIcEQHF text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wgOhZ5yOSgIcEQHF .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wgOhZ5yOSgIcEQHF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wgOhZ5yOSgIcEQHF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wgOhZ5yOSgIcEQHF .sequenceNumber{fill:white;}#mermaid-svg-wgOhZ5yOSgIcEQHF #sequencenumber{fill:#333;}#mermaid-svg-wgOhZ5yOSgIcEQHF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wgOhZ5yOSgIcEQHF .messageText{fill:#333;stroke:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wgOhZ5yOSgIcEQHF .labelText,#mermaid-svg-wgOhZ5yOSgIcEQHF .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .loopText,#mermaid-svg-wgOhZ5yOSgIcEQHF .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wgOhZ5yOSgIcEQHF .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wgOhZ5yOSgIcEQHF .noteText,#mermaid-svg-wgOhZ5yOSgIcEQHF .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wgOhZ5yOSgIcEQHF .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wgOhZ5yOSgIcEQHF .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wgOhZ5yOSgIcEQHF .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wgOhZ5yOSgIcEQHF .actorPopupMenu{position:absolute;}#mermaid-svg-wgOhZ5yOSgIcEQHF .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wgOhZ5yOSgIcEQHF .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wgOhZ5yOSgIcEQHF .actor-man circle,#mermaid-svg-wgOhZ5yOSgIcEQHF line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wgOhZ5yOSgIcEQHF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop decode 循环(与其他请求拼批) POST /v1/chat/completions(流式) 路由解析 / 采样参数与约束(JSON schema / GBNF) 分配或加入一个槽位 prefill:整段 prompt 前向,写入 KV cache 每请求每步 1 token(-fa auto) logits 采样(受 schema/语法约束) SSE 流式吐 token 请求完成,槽位让出 finish

4.3 误区清单

  • ❌ 误区一:把 GitHub Releases 当稳定版 。docs/release.md 写明:正式发布只打语义化 tag,Releases 页面上挂的是夜间构建(b 号)。生产环境要自己固定 commit/tag 构建,或接受夜更节奏。
  • ❌ 误区二:直接喂 2023 年的老 GGML 文件 。仓库专门留着 convert_llama_ggml_to_gguf.py 做旧格式迁移,说明踩的人不少。
  • ❌ 误区三:忽略 -hf 的默认量化档 。-hf <repo> 不带 :quant 后缀时拉的是仓库默认包,大小可能与你想的不同;要指定档位写全 -hf ggml-org/Qwen3.5-0.8B-GGUF:Q4_K_M 这种形式(docs/models.md 的 [:quant] 语法)。
  • ❌ 误区四:MoE 模型按参数总量估显存 。MoE 的专家权重可以留 CPU(--cpu-moe,拆解见 2.4),"激活参数少、总参数大"的模型实际显存需求与传统稠密模型估法完全不同。
  • ❌ 误区五:把"能跑"当"跑得快"。分层卸载让你塞得下超显存的模型,但跨 PCIe 搬张量的代价真实存在;要快还是要模型大部分进 GPU。

五、生态位:事实标准是怎么炼成的

5.1 ggml 家族:一套范式复制到各模态

Gerganov 系项目共享同一套思想:ggml 张量库 + GGUF 容器 + 纯 C/C++ + 全平台后端。whisper.cpp (语音识别)是最早的兄弟项目,stable-diffusion.cpp (图像生成)随后;2026 年的 audio.cpp (110+ 音频模型家族,见姊妹篇)把这套范式扩展成了音频全任务运行时。家族项目的共同点值得显式成句:它们之间共享的是"格式与后端生态",而不是模型加载代码 ------audio.cpp 在 external/ 引入 ggml、复用 GGUF,但模型实现是独立工程(audio.cpp 篇 2.1 已钉过这条边界)。

这张图值得停一眼,因为它画的是全家族的"公分母"。无论 llama.cpp 跑语言、whisper.cpp 跑语音、还是 audio.cpp 跑 110+ 音频模型家族,Transformer 与各类编码器的一切计算最终都坍缩到图上这一个动作------矩阵乘法 :注意力打分是矩阵乘(3.2 的 Q×K、加权 V),线性层投影是矩阵乘(Wq/Wk/Wv 也是矩阵乘),连 2.2 的 GGUF 张量目录,管理的本质就是"哪些矩阵、以什么数值类型参与矩阵乘"。ggml 的全部存在感就押在把这一个原语在 17 个后端上做到最快 :NVIDIA 写 custom CUDA kernel(README 原文功能点),Apple 上 NEON + Accelerate + Metal 全套伺候("一等公民"),x86 吃 AVX/AVX2/AVX512/AMX,RISC-V 有 RVV------2.1 架构图里挂在 ggml 层下的那排后端,比拼的正是这门手艺。出处照实说明:图取自 llama.cpp 仓库 media/matmul.svg(无文字标注的官方示意素材,仓库文档正文并未引用它),本篇取其"最基础算子"的表意安放在此。

5.2 建在 llama.cpp 之上的产品层

llama.cpp 的引擎定位使它成了"端侧 AI 应用的事实内燃机":Ollama 把它包成 ollama run 的极简体验,LM Studio 给它配了完整桌面 GUI,Jan、GPT4All 等开源客户端同样构建其上。这个分层对选型的含义是:喜欢命令行和可控参数的人直接用 llama.cpp;要现成产品体验的人用上层应用,但要明白底下跑的是同一套引擎与 GGUF------换上层的成本远小于换引擎。

5.3 选型对比:llama.cpp / vLLM / MLX / Ollama

还是贯穿案例那件事:给团队提供一个本地 LLM 服务。四条路各自的最优区间:

维度 llama.cpp vLLM MLX Ollama
主场 单机/端侧,硬件覆盖最广 数据中心高并发吞吐 Apple Silicon 原生 零门槛桌面体验
量化 GGUF 原生定义者 支持(作为权重格式之一) 自有格式为主 底层即 llama.cpp
API OpenAI + Anthropic 兼容、语法/JSON 约束、function calling OpenAI 兼容、高并发调度(PagedAttention) 生态库为主 OpenAI 兼容(封装层)
适合谁 要控制感、要跨平台、要嵌入产品 要吞吐、要多用户服务化 只要 Mac 且追极致 Apple 性能 要"五分钟跑起来"

我们仓库里的实证可以互链:Qwen3.8-27B RTX 5090 部署篇走的是 vLLM 路线(GPU 服务器、NVFP4、并发服务化);R2T2 M1 端侧部署与评测篇走的正是 llama.cpp 路线(Mac Metal、GGUF Q8_0、自建扩展六步走)。同一个仓库里两条路都被实操过,本身就是"按场景选引擎"的最好教材。

5.4 治理与社区:AI 时代的开源工程样本

三个值得单独记录的治理事实:

  1. AGENTS.md 的 AI 代码政策 (仓库原文):"AI-generated code is allowed. What is not allowed is submitting code you do not understand. You are 100% responsible for every line, however it was produced."(允许 AI 生成代码;不允许提交你不懂的代码;每一行你负全责。)这是大型开源项目把 AI 协作规范写进仓库的标志性样本。
  2. 仓库自带 agent skills :skills/ 目录有 add-new-model、code-review、slice-model、update-fusion-table 四个维护技能------把"加一个新模型架构"这类高频工程流程做成了 AI 可执行的作业指导书。
  3. 一天七个夜间版的节奏(2026-10-21 releases 实况)配 AGENTS/CLAUDE 双指令文件:这个项目把"高频合入 + AI 辅助维护"当成了工程方法本身,而不是点缀。

5.5 许可证:引擎 MIT,模型另算

与 audio.cpp 篇的"三张皮"完全同构:llama.cpp 本体 MIT(商用最宽松档);它加载的模型权重各随其发布方许可 ;GGUF 转换件是衍生作品、随上游模型许可。用 ggml-org 官方命名空间的模型同样要逐个查模型卡。判断口径直接沿用audio.cpp 篇 5.4 的三层表,此处不重复。

六、一句话总结

回到贯穿案例那条命令:llama serve -hf ggml-org/Qwen3.5-0.8B-GGUF------一行敲下去,Hugging Face 的模型变成你机器上的 OpenAI 兼容服务加 WebUI。这条命令背后是三年半攒出的三层蛋糕:ggml 用 17 个后端管"在哪算",libllama 用 138 个架构标识管"怎么搭",工具链管"怎么用" ;快是量化、KV cache、Flash Attention、continuous batching、投机解码一叠机制抠出来的,而生态霸权来自 GGUF 把"权重+分词器+说明书"压成一个谁都能认的文件。它和 audio.cpp 分享同一个判断:换掉的不是模型,是模型下面的运行时------只是这一次,运行时本身就成了标准。

资料来源

相关推荐
星野云联AIoT技术洞察3 天前
设备手册离线 RAG:先管住版本、权限和引文,再谈本地大模型
llama·rag·设备告警·rag知识库·设备运维·设备离线·设备手册
智码看视界4 天前
开源大模型追踪:TurboQuant量化指南:128K上下文不再爆显存,KV缓存3-bit免重训
google·开源大模型·llama.cpp·显存优化·长上下文推理·turboquant·本地部署 开源大模型
晚风吹北巷5 天前
基于LLama-Factory 进行Lora微调
llama
你都会上树?5 天前
本地部署模型 llama + opencode + Qwen3.8-32b
llama
夜瞬6 天前
从零微调 Qwen3-8B:基于 LoRA 与 LLaMA-Factory 打造专属 AI 编程助教
人工智能·python·llama
神奇的代码在哪里7 天前
没有独显也能跑多模态:MiniCPM-V 4.7-1B 本地 OCR 实测(附源码)
ocr·多模态大模型·llama.cpp·gguf·minicpm-v
Victor Zhong8 天前
llama.cpp 部署大模型到 Ubuntu 台式机 SOP
linux·ubuntu·llama
容沁风8 天前
llama.cpp浏览器访问404
win10·llama.cpp·web-ui
空空dream9 天前
07 | FastAPI + llama.cpp 接入 Langfuse监控Prompt请求
prompt·fastapi·llama