第 8 章:后端——GPU 探测与原生推理进程¶
一、Go 代码不做矩阵乘法¶
Ollama 的 Go 运行时负责 orchestration,实际推理由原生后端完成:
Go server
→ llm.LlamaServer interface
→ llama-server 子进程(GGUF 主路径)
→ llama.cpp / ggml backend
→ CPU / Metal / CUDA / ROCm / Vulkan
Apple MLX 特殊路径
→ x/mlxrunner
llm.LlamaServer 是这一边界的关键:它把加载、ping、chat、completion、embedding、tokenize、memory 等能力统一成 Go 接口。
二、启动时先探测 GPU¶
server.Serve 在启动 Scheduler 后调用 discover.GPUDevices,记录设备详情,再根据总 VRAM 设置默认 context:
| 可用总 VRAM | 快照中的默认 context |
|---|---|
| ≥ 47 GiB | 262,144 |
| ≥ 23 GiB | 32,768 |
| 其他 | 4,096 |
这是“默认值”而不是硬限制;用户显式设置的 context 仍应由 options 和模型训练上限校验。这个策略把硬件差异提前转成服务层的默认参数,减少每个 handler 重复判断。
三、discover 的任务¶
discover/ 不只是列出显卡名称,它要回答 Scheduler 的资源问题:
- 设备类型和 ID。
- 总内存与可用内存。
- 是否支持某种 backend。
- GPU library/compatibility 状态。
- CPU 指令集和系统信息。
- 多 GPU 能否一起参与加载。
这些信息进入 ml.SystemInfo、ml.DeviceInfo,再传给 llm.Load。
四、runner 是独立进程¶
llm.NewLlamaServer 创建的是一个包装对象,实际 runner 通常是一个 HTTP 服务子进程。Go 侧通过端口/进程管理完成:
创建 runner 配置
→ 启动 llama-server
→ WaitUntilRunning / Ping
→ Completion / Chat / Embedding HTTP 调用
→ 读取流式响应
→ Close / 进程回收
独立进程有几个收益:native crash 不会直接破坏 Go server;不同模型可以拥有不同 runner 参数;Go 代码不需要把 C ABI 封装成所有推理细节。
代价是进程启动、端口、日志、跨平台信号与超时都必须严谨处理。
五、上下文和 batch 的联动¶
显存消耗不只由模型权重决定,还和 context、batch、并发 slots、KV cache 类型、flash attention 有关。Scheduler 会计算 effective context,并在自动模式下调整 generation batch。
模型训练 context
∩
用户/默认 num_ctx
∩
设备容量预测
→ effective context
→ batch / parallel / KV cache
→ runner 启动参数
llm.NewLlamaServer 还会在请求 context 大于模型训练 context 时做保护性截断到训练上限,避免向下游传入明显无效的配置。
六、媒体输入¶
llm.MediaData 统一承载 image/audio 数据和 ID;server prompt 层负责将 API 消息中的图片映射到媒体数组,native runner 再按 projector/模型能力处理。这样 API 的 Message.Images 不直接暴露给 C/C++ 进程,而是先转成 runner 可理解的媒体协议。
七、日志与秘密过滤¶
llm/server.go 中的 filteredEnv 对 CUDA/ROCm/GGML 等环境变量做结构化日志,同时对 API、KEY、TOKEN、SECRET、PASSWORD 等 key 进行 redaction。这是后端层的实用安全边界:调试 GPU 加载需要环境信息,但不能把凭据打进日志。
八、MLX 路径为什么单独存在¶
runner/runner.go 当前将 --mlx-engine 路由到 x/mlxrunner.Execute;这表示 MLX 不是在主 llama-server 路径中“隐式切换”,而是一个显式 runner engine。显式边界便于在 Apple Silicon 上使用专门的模型加载/推理实现,同时保留主路径的稳定性。
九、设计取舍¶
- 进程隔离换来稳定和跨语言,但增加 IPC 生命周期复杂度。
- 接口化 LlamaServer换来后端替换能力,但每个实现都要满足相同的加载/流式语义。
- 按 VRAM 设默认 context提升开箱即用,但仍必须允许显式参数覆盖。
- GPU 探测集中在 discover让 Scheduler 只消费能力,不关心平台探测细节。