跳转至

第 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.SystemInfoml.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 只消费能力,不关心平台探测细节。