Ollama:把“本地模型”做成一个运行时¶
这不是 Ollama 使用手册,而是一份面向工程师的源码精读:从
ollama命令进入,追到 HTTP handler、模型文件、调度器、原生 runner,再回到 Agent 和兼容协议。
先给结论¶
Ollama 的核心不是“启动一个模型进程”,而是把一组不稳定的资源统一成一个可调用的本地服务:
- 模型资源可能是多个 Blob、多个架构、多个量化、多个 projector 和 adapter。
- 硬件资源可能是 CPU、Metal、CUDA、ROCm、Vulkan 或 MLX,显存和上下文容量都不一样。
- 请求资源可能是一次短 completion、长 chat、embedding、工具调用、图像输入,甚至是 cloud 模型代理。
- 客户端协议可能是 Ollama 原生 API、OpenAI API、Anthropic Messages API 或内部 Agent 接口。
所以它的真实职责是:
flowchart LR
C[CLI / SDK / 兼容客户端] --> H[HTTP Handler]
H --> P[Prompt / Protocol Adapter]
H --> S[Scheduler]
S --> M[Model + Manifest]
S --> R[Llama Server / MLX Runner]
R --> G[CPU / GPU Backend]
M --> B[Blob Store]
H --> E[NDJSON / SSE Stream]
12 章阅读路线¶
| 章节 | 要回答的问题 |
|---|---|
| 1 | Ollama 解决的工程问题是什么?代码边界如何划分? |
| 2 | 一个 ollama run 如何变成 CLI 命令和交互会话? |
| 3 | API 路由如何把原生协议、OpenAI、Anthropic 接到同一套 handler? |
| 4 | 一次 /api/chat 怎样完成模型解析、prompt、runner、流式响应? |
| 5 | Scheduler 如何管理加载、复用、并发、显存不足与卸载? |
| 6 | 模型为什么是 Manifest + Blob,而不是一个文件? |
| 7 | Safetensors、PyTorch 与 GGUF 如何进入统一模型格式? |
| 8 | 如何探测 GPU 并启动合适的原生推理后端? |
| 9 | Ollama 内置 Agent 如何执行工具、Skills、审批和多轮循环? |
| 10 | 为什么要截断工具结果、做上下文压缩,并保留可观察事件? |
| 11 | 兼容 OpenAI/Anthropic 与 cloud passthrough 的边界在哪里? |
| 12 | App、测试和扩展点如何把这套运行时变成产品? |
最短源码路径¶
第一次读源码,建议沿着这条路径走:
main.go
→ cmd.NewCLI
→ cmd.RunServer / cmd.RunHandler
→ server.Serve
→ server.GenerateRoutes
→ server.ChatHandler
→ server.scheduleRunner
→ Scheduler.getRunner / load
→ llm.NewLlamaServer
→ llama-server subprocess
然后再回头读模型物化路径:
cmd.PullHandler
→ server.PullHandler
→ manifest / blobs
→ server.GetModel
→ server.Model
→ scheduler.load
最后读 Agent 路径:
cmd/agent_tui.go
→ agent.Session.Run
→ chatRound
→ executeToolCalls
→ maybeCompact
→ EventSink
你应该带着什么问题读¶
- 为什么 HTTP handler 不直接启动模型,而要经过 Scheduler?
- 为什么模型配置、模板、量化和权重要拆成不同 layer?
- 为什么流式响应在 Ollama 内部是 NDJSON,在 OpenAI 层变成 SSE?
- 为什么 chat handler 里既有 Go template,又有 llama.cpp 的 Jinja 路径?
- 为什么工具执行会被放进一个独立的
agent.Session,而不是塞进 HTTP server? - 当上下文太长时,系统怎样在“不破坏 tool call 配对”的前提下继续运行?
后续章节会用实际文件、函数和数据结构回答这些问题。