
最近团队在做内部知识库的智能问答数据要求全部留在内网不能往外传。折腾了一圈云上接口和各种封装平台之后我把推理部分全部收敛到了 Ollama 上。这套基于 Go 语言开发的大模型推理应用一条命令就能拉起各种开源模型同时覆盖了模型微调产物的加载、自定义词库、RAG 检索增强生成真正把大模型变成了一个可以深度定制的本地基础设施。这篇文章把我实际落地的过程、踩过的坑、还有关键参数选择都写出来希望对正在做本地化 AI 应用的同学有帮助。1. 先搞清楚Ollama 到底是个什么东西1.1 一句话理解 OllamaOllama 本质上是一个本地大模型推理服务。你不需要自己写模型加载、显存管理、请求排队这些胶水代码它把这些事情全部封装好暴露出一组 HTTP 接口。安装完之后ollama run 某个模型名就能直接在命令行里开始对话ollama pull 模型名就能从模型仓库拉取开源模型。它给我最直观的感受是把过去“下载模型权重 配置 Python 环境 安装推理框架 写 API 服务”这一长串步骤压缩成了几条命令。尤其在内网部署场景我只需要一个二进制文件加模型文件就能在完全离线的机器上把推理服务跑起来。RAG 和微调之所以能在 Ollama 上顺畅落地核心原因是它提供了两套关键能力一是模型自定义机制通过 Modelfile 可以将微调产物打包成独立模型二是 API 接口应用层可以通过 JSON 请求完成生成、嵌入向量化等操作这让它天然适合被外部业务系统集成。1.2 为什么 Go 语言这件事很重要Ollama 选择用 Go 开发对使用者来说不是个无关紧要的细节它直接决定了部署体验和运维成本。Go 编译出来的产物是单一静态二进制文件不依赖目标机器上的 Python 版本、CUDA 工具链这些乱七八糟的环境。我在一台只有 8G 内存的旧工作站上部署时就是拷贝了一个二进制和模型目录过去直接启动前后不到五分钟。这在 Java 或 Python 生态里很难做到这么干净。Go 的并发模型对推理服务也很有价值。一个 Ollama 实例常常要同时处理多个模型的加载、多路请求的调度、模型换入换出的内存管理Go 的 goroutine 和 channel 让这类高并发 IO 密集场景写起来更从容运行时开销也更小。实际压测下来纯 API 层请求的响应延迟很稳定不会有明显的抖动。1.3 和自建推理服务相比它解决了什么如果不用 Ollama自己搭一套推理服务通常要面对几个坎模型权重格式不统一不同框架各有各的加载方式GPU 显存管理要自己写模型换入换出的时机很难把握多模型并存时端口、进程、日志全都需要处理。Ollama 把这些全部收口了。它提供的ollama create命令很关键能基于一个基础模型和 Modelfile 配置文件生成新的模型版本。这个能力让微调产物和词库配置都能被纳入统一的模型管理体系。比如我给不同业务线做了三套定制模型本质上是同一套推理服务在跑但对外暴露的是三个不同的模型名上层应用调用起来非常自然。2. 微调模型不是 Ollama 训模型而是让 Ollama 吃下你的微调产物2.1 先理清微调和推理是两件事很多刚接触 Ollama 的朋友会误以为它能直接训练模型其实不是。Ollama 是推理运行时它的职责是把已经训练好的模型快速跑起来。模型微调是在训练阶段完成的通常要借助专门的开源微调框架。理解这个边界很重要。你的工作流是先在训练阶段用领域数据微调出一个模型适配器再把这个适配器挂到 Ollama 上作为新模型推理。Ollama 做的是后半段但它对微调产物的支持做得足够好只要你把训练结果转换成它支持的格式就能无差别调用。2.2 从训练产物到 Ollama 模型完整三步第一步训练 LoRA 适配器。以常见做法为例选择一个合适的开源基座模型拿着几千条领域问答数据用支持 LoRA 的微调框架训练出一个适配器文件。这个阶段不涉及 Ollama完全在训练环境里完成。第二步格式转换。Ollama 的模型格式基于 GGUF所以需要把训练出的适配器文件转成 GGUF 格式。这一步通常使用官方提供的转换脚本完成转换时要注意基座模型的架构标识必须匹配。我在转换过程中遇到过架构不匹配直接报错的情况重新确认基座模型版本后才通过。第三步用 Modelfile 打包。Modelfile 的写法和 Dockerfile 非常像核心就两行FROM /data/models/qwen-base.gguf ADAPTER /data/models/lora-adapter.gguf写好之后执行ollama create my-domain-model -f Modelfile这条命令会生成一个名为my-domain-model的新模型之后ollama run my-domain-model就能直接在对话里看到微调后的效果。API 调用时也只需要把请求体里的 model 字段改成这个名字。2.3 实践中的选型建议与参数取舍微调相关参数里我踩过最深的坑是量化精度问题。如果基座模型已经是 4bit 量化再叠加微调适配器领域能力会有明显折扣。我的建议是如果资源允许微调时基座尽量用更高精度的版本推理时再量化如果只能在量化基座上做就要适当降低训练学习率并且用评测集验证效果不滑坡。LoRA 的 rank 值也需要权衡。rank 太小模型记不住领域特征回答会泛化rank 太大训练和推理的开销都上去了还容易过拟合。我一般从 8 开始试数据量少用 8数据量过万再考虑 16效果不明显时先加数据而不是加 rank。同时挂多个 LoRA 到同一个基座是 Ollama 的一个实用的玩法。你可以给法律、医疗、代码分别训练一个小适配器然后用不同的 Modelfile 分别 create 出三个模型推理服务底层共享同一个基座权重显存占用不会简单翻三倍。这点在真实项目中节省了不少资源。3. 词库支持三层理解别再把它当成训练词表3.1 模型层词表决定体验选对基座比折腾词库重要词库这个词在 Ollama 场景里很容易被误解。它不是让你给模型加几个词语而是涉及从模型内部到应用侧的多个层面。最底层的是模型的 tokenizer 词表。Ollama 本身不提供重新训练词表的能力但不同模型的词表设计差异很大。有些模型对中文的支持明显更好生僻字编码得更合理切词后的语义碎片更少。如果你的业务涉及大量专业术语选基座的时候要特别关注它对中文分词的处理能力。词表大小直接影响推理效率。词表大了softmax 计算量大推理变慢词表小了长文本被切成大量碎片上下文窗口容易被无效 token 填满。所以这里我的经验是优先选在目标语言上优化过的基座模型而不是自己去做词汇层面的补丁事倍功半。3.2 Modelfile 层模板、停止词、参数就是你的应用词库Ollama 真正让你自由操控的是 Modelfile 里的 TEMPLATE、SYSTEM 和 PARAMETER。在这里你可以把业务词库变成一个动态约束系统。我在一个客服场景里做过这样的配置把公司内部所有产品线的标准说法和维护手册里的术语解释写进 SYSTEM 指令里并明确要求回答时必须优先引用这些术语。效果立竿见影模型的回答明显更规范不再出现自己发明的表述。FROM my-base-model SYSTEM 你是一名产品售后助手。回答时必须遵守以下术语表 1. 复购率 用户再次购买产品的比率统一说法为“复购率”。 2. 工单等级分为 P0/P1/P2P0 为最高优先级必须 15 分钟内响应。 请严格使用以上定义不要自行发挥。 PARAMETER temperature 0.3 PARAMETER stop |im_end|这一层本质上是把词库从静态文件变成了运行时约束让模型在生成时始终被这些规则兜底。对于没有条件做完整微调的团队这是性价比最高的定制手段。3.3 应用层词库和 RAG 知识库的关系词库最常见的落地场景其实是 RAG。大家常说的“给模型加词库”很多时候真正需要的是一套知识库检索系统用户提一个问题系统先从本地语料库中检索相关内容再把检索结果作为上下文注入提示词让模型基于这些内容回答。词库和知识库的边界需要分清。词库是对关键词做规范映射比如把“电脑”映射到“计算机”把“补单”映射到“售后补发货”知识库则是一批完整的文档切片提供事实性的背景信息。两者配合使用时词库负责给用户问题做预处理知识库负责给模型找答案素材链路清晰效果也稳定。4. RAG把私域知识灌进本地推理链路4.1 为什么要做本地 RAG通用模型的训练数据有截止时间企业内部的新制度、新流程、产品手册它根本不知道。直接问模型要么答错要么编造。RAG 就是把你的语料库变成模型的外部记忆在推理前检索相关内容塞进上下文。选 RAG 而不是重新训练核心原因是成本低、更新快。训练一次模型的成本和时间都不是小团队能接受的而换一份文档、加几个词条RAG 体系里几分钟就能生效。尤其是内网部署场景RAG 的全部流程都在本地完成敏感数据不出门合规压力小很多。Ollama 在 RAG 链路里承担的角色不只是生成它还提供文本向量化接口。你可以用同一个服务完成文档切片的嵌入计算和最终回答生成少维护一个外部依赖。整套链路跑起来后所有组件都能在离线环境内闭环。4.2 完整链路拆解从文档到增强提示词一个完整的 RAG 流程包含五个环节文档加载、文本切片、向量化、向量检索、生成增强。文档加载阶段把 PDF、Word、Markdown 等格式的内容抽成纯文本。这里有个容易被忽略的细节PDF 里的目录、页眉页脚会污染切片质量清洗环节要专门处理。文本切片是效果的关键。切片太大检索容易把无关内容带进来占用上下文窗口切片太小语义被切碎召回率会下降。我用的策略是固定长度加重叠让相邻切片保留一定重叠区域这样跨切片的语义不会断裂。向量化阶段通过 Ollama 的嵌入接口把每个切片转成向量存进向量库。检索时把用户问题也转成向量用余弦相似度找出最相关的几个切片。最后把切片内容和原始问题一起拼成增强提示词调用生成接口得到答案。4.3 实操一个最小可用的 Go 知识库问答工具我这边项目是 Go 技术栈所以直接写了一个小工具把全链路串起来。先看核心代码结构。文本切片的部分func splitText(text string, chunkSize, overlap int) []string { var chunks []string runes : []rune(text) for i : 0; i len(runes); i chunkSize - overlap { end : i chunkSize if end len(runes) { end len(runes) } chunks append(chunks, string(runes[i:end])) if end len(runes) { break } } return chunks }这里用 rune 做切片是因为中文按字节切会把汉字切碎计算长度必须按字符而不是字节。chunkSize 我常用 500overlap 用 80这是一个比较均衡的起点。向量化和检索部分通过 HTTP 调用 Ollama 的嵌入接口func getEmbedding(text string) ([]float64, error) { reqBody, _ : json.Marshal(map[string]string{ model: my-embedding-model, prompt: text, }) resp, err : http.Post(http://localhost:11434/api/embed, application/json, bytes.NewReader(reqBody)) // 解析响应中的 embedding 字段 }向量检索我为了轻量化没用外部向量数据库直接内存里算余弦相似度知识库体量在几万条切片以内完全够用。再配合生成接口把命中的切片拼进提示词func askWithContext(question string, contexts []string) string { contextText : strings.Join(contexts, \n\n) prompt : fmt.Sprintf(请基于以下资料回答问题\n\n%s\n\n问题%s, contextText, question) // 调用 /api/generatemodel 设为业务模型 }这套代码跑通后内部同事问“新产品退货流程是什么”模型就能基于本地文档给出有据可查的回答而不是自由发挥。4.4 关键参数速览RAG 效果不好时别急着换模型先调参数。我整理了最有用的几个参数作用我的推荐值调整方向chunk_size切片字符数500检索不准就调小答案不完整就调大overlap切片重叠字数80语义断裂时加大top_k检索返回的切片数4答案空洞就加大上下文超长就减小temperature生成随机性0.3事实问答调低创意生成调高num_ctx上下文窗口长度4096 起提示词总长度超过窗口时加大参数之间要联动看。比如你把 num_ctx 拉到了 8192但是 top_k 返回了 10 个切片加上原始问题提示词可能直接爆掉窗口。RAG 调优的核心就是在上下文长度和检索精度之间找平衡改一个参数要观察其他指标的连锁反应。5. 常见问题与排查技巧实录5.1 模型加载慢、切换频繁Ollama 的默认行为是按需加载模型请求来了才加载进显存空闲一段时间后会释放。这就导致一个问题多个模型轮流调用时每次都有加载等待时间体感很糟糕。我的做法是设置环境变量把模型常驻内存的时间拉长。启动时加上OLLAMA_KEEP_ALIVE-1模型就一直驻留不再自动释放。如果机器内存紧张可以设成具体秒数比如OLLAMA_KEEP_ALIVE600让模型空闲十分钟后再释放。多模型同时在线的场景注意用ollama ps看当前显存占用。Ollama 默认限制同时加载的模型数量超出的会排队换入换出这个顺序可以通过调整请求频率和应用层逻辑来控制。5.2 显存不够怎么办本地模型最烦的就是显存报错。先看是不是并发参数开太大了OLLAMA_NUM_PARALLEL默认允许多个请求并行处理如果显卡不够大把它设成 1强制串行处理能显著降低峰值显存。另外检查一下上下文长度。num_ctx设置得越大KV Cache 占用的显存就越多。一个很常见的现象是对话聊到一半突然报错就是上下文把显存撑爆了。把num_ctx从 8192 降到 4096通常能缓解。还不行就换量化更深的模型文件。量化等级越高显存占用越小但推理质量会有轻微损耗。对内部知识库问答场景我实测下来 4bit 量化的质量下降在可接受范围内显存却几乎省了一半。5.3 中文场景检索效果差RAG 检索命中不准大概率不是模型问题而是词和切片的问题。中文不像英文有天然空格分词一句话里核心信息可能集中在几个关键词上。我的经验是维护一个应用层词库先对用户问题做关键词提取和同义扩展再送进向量检索。比如用户问“你们的退款规则是什么”词库里如果已经有“退款 退货 售后 退款政策”这组映射扩展后的查询向量会更接近文档切片里的真实表述召回率会明显提升。这也呼应了前面说的词库和 RAG 是配合关系不是二选一。5.4 上下文长度被截断生成结果戛然而止最常见的原因是提示词加历史对话超出了模型的上下文窗口。Ollama 端默认窗口可能没你想的那么大应用层拼提示词时要把长度计算进去超长时做截断或者丢弃早期的对话记录。问题排查命令/方法解决方案模型加载慢ollama ps看驻留状态设置OLLAMA_KEEP_ALIVE-1显存溢出查看日志中的 CUDA OOM调低OLLAMA_NUM_PARALLEL或num_ctx检索命中差打印检索出的切片内容增加词库同义扩展、调整 chunk 参数输出被截断计算提示词 token 数限制历史轮次、调大num_ctx端口被占用netstat查看监听状态用OLLAMA_HOST127.0.0.1:新端口启动还有一个调试小技巧启动时加OLLAMA_DEBUG1Ollama 会输出详细的请求日志和显存信息排查问题时要比瞎猜快得多。我在实际部署中最深的体会是Ollama 本身只是一个稳定的推理底座真正拉开差距的是你在它外面搭建的应用层微调适配器怎么管理、词库怎么设计、RAG 参数怎么调这些才是决定一个本地 AI 系统好不好用的关键。别指望装个 Ollama 就万事大吉把它当成一个可以深度集成的基础设施来设计前期多花点时间规划后面维护能省下巨大的精力。最后再分享一个小技巧Modelfile 一定要用版本管理工具管起来尤其是里面维护的 SYSTEM 词库和参数调整记录。我见过太多次线上模型提示词被人临时改过两周后全组都说不清现在跑的是哪套配置过错排查起来让人崩溃。把 Modelfile 当成代码来对待你的 Ollama 项目才能真正稳定运行下去。