第3章:Ollama源码获取与结构分析
好,咱们直接进入正题。这一章我带你亲手把Ollama源码拉下来,然后拆开看看里面到底装了些什么东西。说实话,我第一次看Ollama源码时也有点懵——项目结构不算复杂,但几个核心模块之间的关系需要理清楚。
3.1 从GitHub拉取源码
拉代码这事儿,看似简单,但有几个坑我得提前跟你说。
我个人习惯这样操作:
# 标准克隆方式
git clone https://github.com/ollama/ollama.git
cd ollama
# 如果你只想看最新代码,不想下载全部历史
git clone --depth=1 https://github.com/ollama/ollama.git
# 别忘了初始化子模块!llama.cpp是作为submodule引入的
git submodule update --init --recursive
嗯,这里要注意——子模块一定要拉。我遇到过有人只拉了主仓库,结果编译时一堆头文件找不到,折腾了半天才发现是submodule没初始化。
拉完代码后,看看分支情况:
git branch -a
目前Ollama主要维护在main分支。我个人建议你切到最新的稳定tag,而不是直接跟main走——main分支有时候会有实验性改动,不一定稳定。
git tag | tail -10 查看最近的版本号。我一般选最新的非rc版本。
3.2 目录结构解析
拉完代码,咱们来看看这个项目的骨架。用tree -L 2看一眼大概:
ollama/
├── api/ # API定义层
├── cmd/ # 命令行入口
├── docs/ # 文档
├── examples/ # 示例代码
├── llm/ # 大模型推理核心(关键!)
├── parser/ # 模型文件解析器
├── progress/ # 进度条相关
├── server/ # HTTP服务端
├── types/ # 类型定义
├── version/ # 版本信息
├── go.mod # Go模块文件
├── go.sum # Go依赖校验
└── Makefile # 构建脚本
说实话,这个目录结构设计得挺清晰的。Go项目该有的都有,没有多余的垃圾。咱们重点看几个核心目录:
| 目录/文件 | 作用 | 我的评价 |
|---|---|---|
server/ |
HTTP API服务,处理用户请求 | 入口层,代码量不大 |
llm/ |
模型加载、推理、卸载 | 核心中的核心 |
parser/ |
解析GGUF等模型文件格式 | 底层支撑,容易被忽略 |
api/ |
定义请求/响应结构体 | 协议层,跨语言调用用得上 |
你想想看,一个AI推理服务,本质上就三件事:接收请求 → 加载模型 → 推理返回。Ollama的目录结构就是按这个逻辑拆的。
3.3 核心模块功能说明
咱们一个一个拆开看。我会结合我在嵌入式移植中踩过的坑来讲。
3.3.1 server模块
server/目录下主要是一个HTTP服务,用的是Go标准库的net/http。它负责:
- 接收用户的RESTful请求(比如
/api/generate) - 解析请求参数
- 调用llm模块进行推理
- 把结果返回给客户端
代码入口在server/routes.go,里面定义了所有路由。我刚开始看的时候觉得这层挺简单的,后来才发现——嵌入式移植时,server层反而是最容易出问题的地方。为什么?因为嵌入式设备的网络栈、内存限制、并发模型跟x86完全不同。
server/routes.go- 路由注册server/routes_internal.go- 内部APIserver/routes_test.go- 测试用例
3.3.2 llama.cpp模块
这个模块藏在llm/目录下,实际上是Ollama的推理引擎。它通过cgo调用C++写的llama.cpp库。
目录结构大致是:
llm/
├── gen_server/ # 生成服务的Go封装
├── llama.cpp/ # 子模块,真正的C++推理代码
├── llm.go # Go层的接口定义
└── llm_test.go # 测试
我记得第一次移植时,最头疼的就是llama.cpp/这个子模块。它本身是一个独立的C++项目,Ollama把它作为submodule嵌进来,然后通过cgo调用。这意味着:
- 编译时需要交叉编译C++代码
- 链接时需要处理各种依赖库
- 运行时需要关注内存对齐、ABI兼容性
-march参数,导致推理速度慢得离谱。后来发现是编译器没启用NEON指令集优化。所以移植时一定要确认目标平台的SIMD支持情况。
3.3.3 model模块
model模块其实分散在几个地方:
types/model.go- 模型元数据结构体parser/- 模型文件解析器(GGUF格式)llm/- 模型加载和卸载逻辑
模型加载的流程大概是:
- 用户指定模型名称(比如
llama3.2:1b) - server层解析请求,传给llm层
- llm层从本地缓存或远程拉取模型文件
- parser层解析GGUF文件头,获取模型参数
- llama.cpp加载模型权重到内存
- 返回就绪状态
嗯,这里有个关键点——GGUF格式。Ollama用的是GGUF(GPT-Generated Unified Format),这是llama.cpp社区推的一种模型文件格式。它把模型权重、分词器、超参数都打包在一个文件里,方便分发和加载。
3.4 核心架构图
说了这么多,咱们用一张图把整体架构串起来。这张图是我自己画的,把Ollama的核心模块和调用关系展示清楚:
这张图你看懂了吗?说白了,Ollama就是一个Go写的胶水层,把C++的推理引擎包装成HTTP服务。移植到嵌入式平台时,我们主要关注llm/和llama.cpp/这两块——因为它们是真正的性能瓶颈所在。
3.5 本章小结
这一章我们干了三件事:
- 拉代码:用git clone把Ollama源码搞到手,别忘了子模块
- 看结构:server是入口,llm是核心,parser是底层支撑
- 拆模块:server管通信,llama.cpp管推理,model管数据
我个人觉得,理解Ollama的架构是移植成功的一半。你想想看,如果你连代码怎么组织的都不知道,怎么改?怎么优化?
下一章咱们就开始动手编译了——我会带你配置交叉编译环境,把Ollama编译成ARM64的可执行文件。嗯,那才是真正考验功力的时候。
公众号:蓝海资料掘金营,微信deep3321