Ollama 深度拆解:当大模型变成「一个文件」——GGUF、4-bit 量化与 llama.cpp 计算图如何重写本地推理的心智模型
2026 年,如果你问一个后端工程师「你本地能跑大模型吗」,答案大概率是「能,Ollama 一行命令」。但如果再追问一句「它到底是怎么把 70 亿参数的模型塞进你那台 16G 内存的笔记本,还能边聊边生成代码的」,绝大多数人会卡壳。
这正是本文要解决的问题。我们要拆的不是一个「模型下载器」,而是一整条被重新设计的本地推理链路:模型文件化(GGUF)→ 权重压缩(量化)→ 无依赖张量引擎(GGML/llama.cpp)→ 键值缓存(KV Cache)→ 发行与服务层(Ollama)。
一个核心观点先抛出来:本地推理从来不是「把云端模型搬回家」,而是一场关于内存带宽、文件格式与分发范式的重新设计。 你以为的瓶颈是算力,真正的瓶颈是「把权重从内存搬到计算单元」的速度。理解了这一点,你才会明白为什么 Ollama 值得被认真对待。
一、背景:大模型「文件化」的范式转移
1.1 从 MaaS 到「模型即文件」
2023 年之前,跑一个大模型的标准姿势是:租 GPU 实例 → 装 CUDA → 配 PyTorch 环境 → 拉 transformers → 写一堆 model.load_state_dict() 和 device_map → 祈祷显存不爆。模型不是一个「东西」,而是一堆散落在 Git LFS、safetensors、配置 JSON 里的碎片,外加一个脆弱的 Python 依赖图。
GGML 生态(llama.cpp)做了一件反直觉的事:把整个模型——权重、分词器配置、对话模板、特殊 token、RoPE 参数——塞进一个二进制文件里,并且让它在没有 PyTorch、没有 CUDA 的纯 C/C++ 环境下也能跑。模型第一次变成了一个可以 mmap、可以拷贝、可以用 cat 看 magic number 的「文件」。
Ollama 在这个基础上又加了一层:把模型文件变成可寻址、可版本化、可一键拉取的「发行物」,就像 docker pull 一样。于是工程师的心智模型从「我得搭一套推理工程」变成了「ollama run qwen2.5:7b」。
1.2 为什么是现在
三股力量在 2024–2026 年交汇:
- MoE 与小模型能力跃升:Qwen、DeepSeek、Llama 系列的 7B/8B 模型在代码与推理上已经能覆盖大量工程场景,本地跑不再只是玩具。
- 量化技术成熟:4-bit 量化把 7B 模型的磁盘与内存占用从 14GB(FP16)压到 ~4.7GB(Q4_K_M),让消费级硬件真正可用。
- 隐私与成本压力:把代码、合同、病历发到云端 API 是不可接受的,而按 token 计费的云端推理在高频场景下成本可观。本地推理把边际成本压到「电费 + 折旧」。
1.3 Ollama 在栈里的真实位置
先戳破一个常见误解:Ollama 自己不写推理内核。它的价值在「发行层 + 服务层」,真正的算力活儿交给 llama.cpp(GGML 张量库)。
你的应用 (Python/Go/JS)
│ HTTP / OpenAI 兼容 API
▼
Ollama Server ← 模型注册、Modelfile、并发调度、流式
│ 子进程调用
▼
llama.cpp runner ← 加载 GGUF、量化反量化、计算图执行
│
▼
GGML 后端 (CUDA / Metal / ROCm / Vulkan / CPU)
所以拆解 Ollama,一半是在拆它的「壳」(怎么管模型、怎么暴露 API),另一半是在拆它脚下的 llama.cpp(怎么把量化后的张量算出来)。下面从文件格式开始,由底向上。
二、核心概念一:GGUF —— 一个模型就是一个文件
2.1 GGML 到 GGUF:一次痛苦的版本升级
最早的 llama.cpp 用 GGML 格式。它有三个硬伤:元数据分散、不支持新特性向后兼容、每次加字段老模型就废。2023 年底,社区引入 GGUF(GPT-Generated Unified Format),核心改进是:
- 单一文件:权重 + 全部元数据在一个文件里,方便分发和
mmap。 - 强类型元数据区:用 key-value 结构存任意扩展信息(对话模板、作者、量化类型、RoPE 基频、Chat template 等),新字段不影响老模型读取。
- 版本化:文件头带
version字段,解析器按版本分支,向前兼容。
2.2 GGUF 的字节布局
一个 GGUF 文件从前往后大致是:
| magic "GGUF" | version (uint32) | tensor_count (uint64) | metadata_kv_count (uint64) |
| metadata KV 区块(交替的 key 字符串 / 类型 / value) |
| tensor info 区块(每个张量的名字、维度、类型、偏移) |
| 对齐填充(默认 32 字节对齐) |
| 张量数据区块(按 tensor info 中的偏移摆放) |
你可以亲手验证一个 GGUF 文件是不是真的「文件化」:
# 看文件头 magic
xxd -l 16 your-model.Q4_K_M.gguf
# 输出前 4 字节应为: 47 47 55 46 → "GGUF"
Ollama 拉下来的模型 blob 其实就是 GGUF 文件,只是被切成内容寻址的 blob 存进 ~/.ollama/models/blobs/。
2.3 元数据里藏着的「灵魂」:Chat Template
GGUF 最被低估的设计是:把对话模板(chat template)直接写进文件。这意味着同一个基座模型,换一个 template 就能变成「助手风格」「代码风格」或「工具调用风格」,而不用改一行推理代码。Ollama 读这个模板来拼装多轮对话的 prompt。
// 伪代码:Ollama 用 Go text/template 渲染对话
// template 来自 GGUF 的 tokenizer.chat_template 字段
tmpl := template.Must(template.New("chat").Parse(metadata.ChatTemplate))
var sb strings.Builder
tmpl.Execute(&sb, ChatContext{
System: systemPrompt,
Prompt: userPrompt,
Response: "", // 生成时为空,让模型续写
})
这就是为什么你 ollama run llama3 直接就能多轮对话——模板已经在文件里了。
三、核心概念二:量化 —— 本地推理的本质是内存带宽战争
这是全文最该记住的一节。
3.1 算力墙是假的,带宽墙才是真的
很多人直觉认为「模型越大越慢是因为算得多」。错。在自回归解码(decode)阶段,每生成一个 token,都要把全部权重从显存/内存读一遍。计算量相对小得多。
算笔账:一个 7B 模型,FP16 权重 = 7×10⁹ × 2 字节 ≈ 14 GB。假设你的显卡显存带宽是 1 TB/s(RTX 4090 约 1 TB/s,A100 约 2 TB/s):
每 token 读取权重的理论时间 = 14 GB / 1024 GB/s ≈ 13.7 ms
而 7B 在 INT8 下的实际乘加计算时间往往只有几毫秒。结论很残酷:解码速度 ≈ 权重总量 / 内存带宽。这就是为什么加显卡算力的收益,远不如「把权重压小」的收益——量化直接等比例降低每次读取的字节数。
工程直觉:本地推理的「提速第一杠杆」永远是先降精度(量化),其次才是堆带宽。
3.2 量化的数学:对称量化
最朴素的线性量化(以 4-bit 为例):
scale = (max(|w|)) / (2^(4-1) - 1) # 对称量化,zero_point = 0
q = round(w / scale) # 存成 int4
w' ≈ q * scale # 推理时反量化回浮点再算
存的是 int4 + 一个 scale(通常每个 block 共享一个 scale)。反量化几乎是免费的——一次乘法。q4_0 就是这种「一个 scale 管一块」的最简方案。
3.3 llama.cpp 的量化家族(挑重点)
| 类型 | 每权重比特 | 特点 | 适用场景 |
|---|---|---|---|
F16 / F32 | 16 / 32 | 无损基准,体积大 | 质量优先、显存充足 |
Q8_0 | 8 | 近无损,体积减半 | 质量敏感、显存够 |
Q6_K | ~6.6 | 高质量,体积更小 | 中端显卡 |
Q5_K_M | ~5.5 | 质量/体积均衡 | 通用 |
Q4_K_M | ~4.85 | 7B 默认首选,质量可接受 | 消费级 16G |
Q4_0 | 4 | 老牌 4-bit,质量略逊于 K 系 | 兼容/古老设备 |
Q3_K_M | ~3.9 | 小体积 | 显存紧张 |
IQ4_XS / IQ3_XXS | 4 / 3 | 重要性矩阵量化,同体积质量更高 | 追求极致压缩 |
3.4 K-quant 与 IQ-quant 的区别(这是深水区)
- K-quant(k-means 量化):把权重切成「超块(super-block)」,用 k-means 思想为一组权重算更精细的 scale/最小值,显著减少大权重被小权重拖累的误差。
Q4_K_M里的M表示「medium 混合」——注意力层用稍高质量量化,FFN 层用更激进的量化,因为经验上 FFN 对量化更鲁棒。 - IQ-quant(importance-matrix 量化):在量化前,用一个 重要性矩阵(imatrix) 统计每个权重对模型输出的影响,把量化误差「花」在影响大的权重上。需要先用
llama-imatrix跑一遍校准语料生成imatrix.dat,再量化。同体积下 IQ 系通常比 K 系困惑度(perplexity)更低。
实践建议:能用
IQ4_XS就用它(同为 4-bit,质量常优于Q4_K_M);显存实在不够再降IQ3_XXS。
3.5 怎么选:质量 vs 体积决策树
显存 ≥ 模型 F16 体积? → 用 F16/F32(真·无损)
显存够 Q8? → Q8_0(几乎无损,体积减半)
消费级 8~16G? → Q4_K_M 或 IQ4_XS(默认)
只有 6~8G? → Q3_K_M / IQ3_XXS
必须在 4G 以内? → IQ2_XXS(质量明显掉,谨慎)
记住:困惑度每涨一点,对应某些长尾任务的质量下滑。量化不是免费的,但它把「跑不起来」变成「跑得动但略糙」,这个trade-off在本地场景几乎总是划算的。
四、核心概念三:GGML 张量库与计算图
4.1 为什么不用 PyTorch
PyTorch 一个 libtorch 就几百 MB,还绑定特定 CUDA 版本。GGML 选择纯 C/C++、零第三方依赖,只依赖操作系统和编译器。这让它能:
- 在 iPhone、树莓派、浏览器 WASM 里跑(本站已发过 pgrust、WASM 相关,思路类似——极简运行时);
- 用
mmap把 GGUF 直接映射进地址空间,省掉「读文件到内存」这一步; - 为每个后端写极薄的算子,最大化利用硬件(Metal 的 GPU、CPU 的 NEON/AVX)。
4.2 后端抽象
llama.cpp 把「计算」和「硬件」解耦:
ggml_tensor 操作 (matmul / rmsnorm / rope / attn)
│ 抽象接口
▼
backend: CUDA | Metal | ROCm | Vulkan | CPU(多线程)
你选 Q4_K_M 的模型,在 Apple Silicon 上走 Metal、在 NVIDIA 上走 CUDA、在纯 CPU 上走 NEON 多线程——上层计算图一行不用改。Ollama 的安装包已经把这些后端编进去了,你只需用 num_gpu 告诉它「卸载几层到 GPU」。
4.3 计算图与并行
前向传播被描述成一张计算图:embedding → N×(RMSNorm → RoPE → SelfAttn → RMSNorm → FFN) → lm_head。llama.cpp 用一个轻量线程池把矩阵乘切成块并行。这就是为什么即便纯 CPU,多核也能吃满——num_thread 控制线程数,默认等于物理核数。
五、核心概念四:KV Cache —— 自回归推理的隐藏成本
5.1 原理
Transformer 自回归生成时,第 t 个 token 的注意力要看到前面 1..t-1 所有 token 的 K、V。如果每次都重算,复杂度是 O(n²)。KV Cache 把每层每个历史 token 的 K、V 缓存下来,新 token 只需算自己的 Q 去查表。
代价是显存随上下文长度线性增长:
单 token 单层 KV 字节 ≈ 2(K,V) × n_head × head_dim × dtype_bytes
7B: 2 × 32 × 128 × 2(F16) = 16 KB / token / layer
× 32 层 ≈ 0.5 MB / token
× 4096 ctx ≈ 2 GB ← 仅 KV Cache,不含权重!
这就是很多人「开长上下文就 OOM」的根源。本站之前的《LMCache 深度拆解》讲的是服务端跨实例复用 KV Cache,而本地场景里 Ollama 解决的是单机 KV Cache 压缩。
5.2 KV Cache 量化
Ollama/llama.cpp 支持把 KV Cache 本身量化,用 type_k / type_v 指定:
| KV 量化类型 | 每元素比特 | 显存节省 | 质量影响 |
|---|---|---|---|
f16(默认) | 16 | 基准 | 无 |
q8_0 | 8 | ~50% | 极小 |
q4_0 | 4 | ~75% | 轻微 |
q2_k | ~2.6 | ~84% | 明显,长上下文慎选 |
经验法则:长上下文(≥8K)必开 KV 量化,优先 q8_0,质量几乎不掉却能砍掉一半显存;资源极度紧张再上 q4_0。
六、架构分析:Ollama 到底做了什么
6.1 三层结构
- 模型库(Model Library / Registry):维护
name:tag→ manifest → blobs 的映射。ollama pull本质是分块下载 GGUF blob(内容寻址,断点续传)。 - 运行时(Runner):下载完
ollama run时,Ollama 起一个 llama.cpp 子进程,把 GGUF 路径、参数、模板传进去,加载进内存/GPU。 - 服务(Server):常驻 HTTP 服务(默认
:11434),暴露/api/chat、/api/generate、/api/embed、/api/create等,并兼容 OpenAI 的/v1/chat/completions。
6.2 存储结构
~/.ollama/
├── models/
│ ├── manifests/ # 每个 name:tag 的清单(指向哪些 blob)
│ │ └── registry.ollama.ai/library/llama3/8b
│ └── blobs/ # 真实的 GGUF 分块(sha256 命名)
│ └── sha256-xxxxxxxx...
└── history/ # 对话历史(可选)
内容寻址的好处:两个模型共享同一个底层权重 blob 时,只存一份;删除一个模型不会误删被别的模型引用的 blob。
6.3 Modelfile:本地推理的「Dockerfile」
Modelfile 是 Ollama 的构建描述,语法刻意模仿 Dockerfile:
FROM ./qwen2.5-7b-Q4_K_M.gguf
# 参数(对应 llama.cpp 的启动参数)
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
PARAMETER num_gpu 35 # 卸载到 GPU 的层数,-1 表示全部
PARAMETER repeat_penalty 1.1
# 系统提示词
SYSTEM """
你是一个严谨的资深后端工程师,回答要给出可运行代码与权衡分析。
"""
# 自定义对话模板(Go template 语法)
TEMPLATE """
{{ if .System }}<|im_start|>system
{{ .System }}<|im_end|>
{{ end }}{{ range .Messages }}<|im_start|>{{ .Role }}
{{ .Content }}<|im_end|>
{{ end }}<|im_start|>assistant
"""
构建并运行:
ollama create myqwen -f Modelfile
ollama run myqwen "用 Go 写一个带超时的 TCP 连接池"
6.4 ollama run 的执行链
resolve name:tag
→ 本地有?直接 load
→ 没有?pull(下载 blobs + manifest)
→ create(按 Modelfile/默认模板建运行时配置)
→ load(启动 llama.cpp runner,mmap 权重,分配 KV Cache)
→ serve(进入交互/API 就绪)
6.5 GPU 卸载的真相:num_gpu
num_gpu 决定把前多少层 Transformer 层卸载到 GPU,其余留在 CPU(用 mmap 的内存 + 多线程算)。这是本地推理最关键的调优旋钮:
num_gpu -1(全卸载):最快,但吃满显存;num_gpu 20(部分):显存省,但 CPU 要算剩下的层,速度降;num_gpu 0:纯 CPU,慢但任意机器可跑。
Apple Silicon 的「统一内存」是个特例:GPU 和 CPU 共享同一块内存,所以「全卸载」几乎不增加显存压力,这也是 Mac 跑本地模型体验好的根本原因。
七、代码实战
7.1 最小可运行示例
# Linux/macOS 一键装
curl -fsSL https://ollama.com/install.sh | sh
# 拉一个 7B 模型并对话
ollama pull qwen2.5:7b
ollama run qwen2.5:7b "用一句话解释 KV Cache"
7.2 用 Python 写流式对话客户端
Ollama 的流式接口每行返回一个 JSON 片段(done:true 时结束)。这样写能拿到逐字输出和性能数据:
import requests, json, sys
def chat_stream(model: str, messages: list, base_url: str = "http://localhost:11434"):
url = f"{base_url}/api/chat"
payload = {"model": model, "messages": messages, "stream": True}
with requests.post(url, json=payload, stream=True, timeout=120) as r:
r.raise_for_status()
for raw in r.iter_lines():
if not raw:
continue
chunk = json.loads(raw)
delta = chunk.get("message", {}).get("content", "")
if delta:
print(delta, end="", flush=True)
if chunk.get("done"):
# eval_duration 单位是纳秒
dur_ns = chunk.get("eval_duration") or 1
tps = chunk.get("eval_count", 0) / (dur_ns / 1e9)
print(f"\n\n[性能] {chunk.get('eval_count')} tokens, "
f"{tps:.1f} tokens/s, "
f"总耗时 {chunk.get('total_duration',0)/1e9:.2f}s")
return
if __name__ == "__main__":
chat_stream("qwen2.5:7b", [
{"role": "system", "content": "你只回答技术问题,言简意赅。"},
{"role": "user", "content": "Go 的 sync.Pool 适合缓存什么对象?"},
])
7.3 用 Go 写一个原生流式客户端
展示不依赖第三方 SDK 时怎么消费 Ollama 的 NDJSON 流:
package main
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
)
type msg struct {
Role string `json:"role"`
Content string `json:"content"`
}
type chatReq struct {
Model string `json:"model"`
Messages []msg `json:"messages"`
Stream bool `json:"stream"`
}
type chatResp struct {
Message msg `json:"message"`
Done bool `json:"done"`
EvalCount int `json:"eval_count"`
EvalDur int64 `json:"eval_duration"` // ns
}
func main() {
body, _ := json.Marshal(chatReq{
Model: "qwen2.5:7b",
Messages: []msg{{Role: "user", Content: "用 Go 实现一个带 context 超时的最小 HTTP 客户端"}},
Stream: true,
})
req, _ := http.NewRequest("POST", "http://localhost:11434/api/chat", strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
defer resp.Body.Close()
scanner := bufio.NewScanner(resp.Body)
scanner.Buffer(make([]byte, 1<<20), 1<<20)
start := time.Now()
for scanner.Scan() {
var c chatResp
if err := json.Unmarshal(scanner.Bytes(), &c); err != nil { continue }
if c.Message.Content != "" {
fmt.Print(c.Message.Content)
}
if c.Done {
tps := float64(c.EvalCount) / (float64(c.EvalDur) / 1e9)
fmt.Printf("\n\n[tokens/s=%.1f, wall=%s]\n", tps, time.Since(start))
}
}
}
7.4 自己量化一个模型(HuggingFace → GGUF → 量化)
这是最能体现「模型文件化」掌控力的实战:把一个开源 safetensors 模型变成你自己的 GGUF。
# 1. 准备 llama.cpp 工具链
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp && pip install -r requirements.txt
cmake -B build && cmake --build build -j
# 2. 转换:safetensors -> F16 GGUF
python convert_hf_to_gguf.py /path/to/your-model \
--outfile model-f16.gguf
# 3. (可选但推荐)生成重要性矩阵,给 IQ 系量化用
./build/bin/llama-imatrix -m model-f16.gguf \
-f calibration.txt -o imatrix.dat
# 4. 量化:用 imatrix 做 IQ4_XS
./build/bin/llama-quantize --imatrix imatrix.dat \
model-f16.gguf model-IQ4_XS.gguf IQ4_XS
# 5. 导入 Ollama
echo 'FROM ./model-IQ4_XS.gguf' > Modelfile
ollama create mymodel -f Modelfile
ollama run mymodel "你好"
注意:
convert_hf_to_gguf.py与llama-quantize的二进制名随版本可能微调,以你 clone 的 llama.cpp 实际为准。量化类型字符串(如IQ4_XS、Q4_K_M)直接作为最后一个位置参数传入。
7.5 在 Ollama 上做工具调用(function calling)骨架
Ollama 本身不强制工具协议,但你可以用语义约定实现:让模型输出结构化 JSON 工具调用,解析后执行再回填。
import requests, json
TOOLS = {
"get_weather": lambda city: f"{city} 今天 22°C,多云",
}
def agent_loop(user_q: str, model="qwen2.5:7b"):
sys = ("你是助手。需要查天气时,只输出一行 JSON: "
'{"tool":"get_weather","args":{"city":"北京"}},不要解释。')
messages = [{"role":"system","content":sys},
{"role":"user","content":user_q}]
# 第一次调用,拿模型决定是否用工具
r = requests.post("http://localhost:11434/api/chat",
json={"model":model,"messages":messages,"stream":False}).json()
content = r["message"]["content"].strip()
if content.startswith("{"):
call = json.loads(content)
result = TOOLS[call["tool"]](**call["args"])
messages.append({"role":"assistant","content":content})
messages.append({"role":"user","content":f"工具返回: {result}。请据此回答。"})
r = requests.post("http://localhost:11434/api/chat",
json={"model":model,"messages":messages,"stream":False}).json()
return r["message"]["content"]
return content
print(agent_loop("北京天气怎么样?"))
这个骨架在生产里会换成更强的 schema 约束(如强制 JSON 模式、重试解析),但核心思路一致:本地模型 + 工具 = 零云端依赖的 Agent。
八、性能优化实战
把前面所有旋钮拧到「对的档位」,本地推理体验能差 3~5 倍。
8.1 num_gpu:卸载层数的调优
# 看模型有几层(以 llama3:8b 为例约 32 层)
ollama show llama3:8b
# 全卸载(显存够就无脑用)
ollama run llama3:8b --num_gpu -1
# 显存紧:先试 20,再 25、30,找到不 OOM 的最大层数
经验:能全卸载就全卸载;Mac 统一内存几乎总能全卸载;x86 独显则看 nvidia-smi 里剩余显存,留 ~1GB 余量给 KV Cache 和框架开销。
8.2 num_ctx / num_batch / num_thread
num_ctx:上下文窗口。越大 KV Cache 占得越多。只开你真用到的长度(默认 2048,长文档摘要设 8192 并开 KV 量化)。num_batch:前向计算的批大小,影响吞吐。本地单用户一般 256~512 即可。num_thread:CPU 线程数。纯 CPU 跑时设成物理核数;有 GPU 卸载时不必拉满,留点给系统。
8.3 KV Cache 量化配置
在 Modelfile 里:
PARAMETER num_ctx 8192
PARAMETER type_k q8_0 # K 矩阵量化
PARAMETER type_v q8_0 # V 矩阵量化
长上下文场景下,这一项常能把「OOM 跑不起来」变成「跑得动」。
8.4 并发与 num_parallel
多用户同时提问时,设 num_parallel(默认 1)提高并发:
export OLLAMA_NUM_PARALLEL=4
代价是显存/KV Cache 要按最大并发数预留。个人单机用默认 1 最稳。
8.5 量化档位决策(再强调一遍)
显存 ≥ F16 体积 → F16
显存 ≥ Q8 体积 → Q8_0
16G 消费级 → Q4_K_M 或 IQ4_XS(首选 IQ4_XS)
8G 以内 → Q3_K_M / IQ3_XXS
极限压缩 → IQ2_XXS(质量明显掉)
8.6 怎么读性能数据
Ollama 流式返回里 eval_count / (eval_duration / 1e9) 是生成速度(tokens/s),这是本地体验的核心指标:
- CPU 跑 7B:通常 5~15 tokens/s(能聊);
- GPU 全卸载 7B:40~120 tokens/s(流畅);
- Apple Silicon M 系列 7B:30~60 tokens/s。
prompt_eval_duration 是「处理输入」的时间,长 system prompt 时占比明显,优化方式是缩短固定前缀、复用历史。
九、总结与展望
回到开头那句话:本地推理不是把云端模型搬回家,而是一场关于内存带宽、文件格式与分发范式的重新设计。
Ollama 把这条链路产品化了:
- GGUF 让模型第一次成为「可分发、可 mmap、可版本化」的单一文件;
- 量化(K-quant / IQ-quant) 把「跑不起来」变成「跑得动」,本质是在跟内存带宽墙做交易;
- GGML/llama.cpp 用零依赖的纯 C/C++ 张量引擎,把推理塞进从手机到服务器的每一种硬件;
- KV Cache 量化 解决长上下文的显存膨胀;
- Ollama 在这之上提供
docker pull式的模型发行与 OpenAI 兼容的服务,让本地大模型真正「可用」。
给工程师的落地建议:
- 个人/隐私敏感场景:Ollama + Q4_K_M/IQ4_XS + 全 GPU 卸载,体验已经能打;
- 高频生产流量、需要批处理与高并发:别硬刚 Ollama,上 vLLM/SGLang(本站《LMCache 深度拆解》里讲的服务端优化那条路);
- 边缘/嵌入式:llama.cpp 直接编进设备,Ollama 偏重,反而用裸 llama.cpp 或适配层更合适。
2026 年的趋势很清楚:模型在「下沉」——从数据中心下沉到笔记本、从云端下沉到边缘、从黑盒 API 下沉成你硬盘里的一个文件。 当你 ollama run 的时候,你启动的不只是一个聊天程序,而是一个正在发生的范式转移。理解它底层那套「文件 + 量化 + 计算图 + KV Cache」的机器,你才真正握住了本地 AI 的方向盘。
延伸阅读提示:本文聚焦本地单机推理与文件格式。若你关心服务端大规模 KV Cache 复用、PD 分离与分布式推理优化,可参考本站的《LMCache 深度拆解》;关心 Rust 重写数据库引擎的极简运行时思路,可参考《pgrust 深度拆解》。