跳转至

Ollama:把“本地模型”做成一个运行时

这不是 Ollama 使用手册,而是一份面向工程师的源码精读:从 ollama 命令进入,追到 HTTP handler、模型文件、调度器、原生 runner,再回到 Agent 和兼容协议。

先给结论

Ollama 的核心不是“启动一个模型进程”,而是把一组不稳定的资源统一成一个可调用的本地服务:

  1. 模型资源可能是多个 Blob、多个架构、多个量化、多个 projector 和 adapter。
  2. 硬件资源可能是 CPU、Metal、CUDA、ROCm、Vulkan 或 MLX,显存和上下文容量都不一样。
  3. 请求资源可能是一次短 completion、长 chat、embedding、工具调用、图像输入,甚至是 cloud 模型代理。
  4. 客户端协议可能是 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 配对”的前提下继续运行?

后续章节会用实际文件、函数和数据结构回答这些问题。