ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DeepSeek多平台部署实战:Ollama、手机端与Docker Open WebUI避坑

DeepSeek多平台部署实战:Ollama、手机端与Docker Open WebUI避坑

简介:面向希望跨设备运行 DeepSeek 大模型的技术爱好者与开发者,这份实操文档围绕多平台部署展开,适合具备基础命令行能力、需要按硬件条件选择方案的人群。正文系统梳理 Ollama 本地安装、模型拉取与终端对话,iPhone 快捷指令及安卓 Termux 的环境配置、密钥填写与指令执行,并说明 Open WebUI 结合 Docker Desktop 完成图形化部署、模型选择下载和对话测试的流程。每个环节均配有清晰的操作指引与关键命令说明,既照顾初学者按步骤操作,也便于有经验者快速定位配置要点,理解服务验证与常见问题处理。资源包仅含 1 个 docx 文件,约 15KB,内容集中,方便查阅和按章节检索;已有 768 人学习,可作为跨平台部署的入门参考和方案对比材料。

1. 从一台没有独显的笔记本说起:DeepSeek 多平台部署最先卡在哪

上周有位读者发来截图,他在一台 16G 内存、没有独立显卡的办公本上折腾了两天 DeepSeek,最后停在Error: model requires more system memory。他试过的路线和我见过的大多数人一样:先在网页端用,再想搬到本地,然后发现本地跑不动大模型,于是退而求其次找轻量方案,最后卡在显存、端口、模型格式这三件事上。AI 模型部署的门槛从来不在“会不会敲命令”,而在“知不知道自己这台机器该选哪条路”。这篇笔记拆的就是这个:同一套 DeepSeek,用 Ollama 在桌面端跑、用快捷指令或 Termux 在手机上跑、用 Docker 加 Open WebUI 做图形化访问,三条路线各自的适用边界、要改的参数,以及我自己翻过车的几个地方。适合手里有机器、想真跑起来而不是只看概念的人。

2. 用 Ollama 先把底跑通:三条安装命令、四档模型与两个调用接口

2.1 为什么先用 Ollama 铺底,而不是自己写推理服务

很多人一上来就想用 transformers 加载权重、自己写 FastAPI 包一层,觉得这样“可控”。这条路不是不行,但它把三件本来可以分开的事绑在了一起:模型权重的下载与校验、推理后端的编译与算子适配、以及服务层的接口设计。只要你换一次量化格式,前面写的东西就得重来一遍。Ollama 的价值在于它把前两件吃掉了——它内部封装了 llama.cpp 那套推理后端,模型按 GGUF 格式分发,带一份 Modelfile 描述模板、停止符、上下文长度这些元信息,对外只暴露一个 11434 端口的 HTTP 接口。

对绝大多数落地场景来说,这个抽象层级刚好。你在本地起的是个能直接curl的服务,而不是一堆 Python 进程;模型切换只是换个名字;后面接 Open WebUI、接自己的业务代码、接手机端,全都是打同一个 HTTP 接口。我一般的做法是:先用 Ollama 把模型跑通、把硬件能承受的规格摸清楚,再决定要不要往更底层走。反过来先啃推理框架,往往在编译环节就把耐心耗光了。

有一点要提前说清楚:Ollama 的默认量化是 Q4_K_M,也就是 4 比特的主流量化档。同一份权重,Q4 和 Q8 的体积差一倍,输出质量在多数任务上肉眼差距不大,但在数学推理和长链条逻辑上能看出区别。DeepSeek-R1 这种带思维链的模型,量化掉太多精度后中间步骤容易跑偏,所以选规格的时候不能只看“能不能塞进内存”,还要看“塞进去之后还能不能推理”。

2.2 三平台安装与拉取:把命令写对

安装这一步本身没什么好讲的,官网按操作系统给包,macOS 给的是 app,Linux 给一条 shell 脚本,Windows 是 exe。真正容易出错的是拉取和启动这一段,尤其是命令里的模型名写错一个字符,报错信息还特别含糊。

# macOS / Linux:官方安装脚本,装完 ollama 会自动注册为系统服务 curl -fsSL https://ollama.com/install.sh | sh # 检查服务是否起来了,正常会返回 "Ollama is running" curl http://localhost:11434 # 拉取并进入交互式对话,7b 是 Qwen2.5 蒸馏版,下载体积约 4.7GB ollama run deepseek-r1:7b # 只想拉不运行,先确认磁盘够不够 ollama pull deepseek-r1:7b # 已经拉过的模型列出来,看名字和体积 ollama list

Windows 上的差别只在终端:PowerShell 里同样跑ollama run deepseek-r1:7b,但如果你是先装了 app 再开终端,可能会遇到命令找不到的情况,因为 PATH 要重开终端才生效。这是个小细节,但足够让人怀疑自己装失败了。

命令背后的逻辑值得说一句。ollama run是“拉取 + 加载 + 进入 REPL”三合一,第一次执行会走 OCI 分发协议去拉层,拉完落到本地模型目录;之后再执行就是直接加载。模型目录的位置按平台不同:Linux 在/usr/share/ollama/.ollama/models,macOS 在~/.ollama/models,Windows 在用户目录下的.ollama\models。如果系统盘紧张,改OLLAMA_MODELS环境变量指到大盘,这一步要在第一次 pull 之前做,事后改要手动搬目录,很容易搬漏。

还有一个常被忽略的参数是监听地址。默认只监听127.0.0.1:11434,本机用没问题,但一旦你要从手机、从 Docker 容器、从局域网另一台机器访问,就必须改成0.0.0.0:11434。Linux 上通过 systemd 覆盖:

# 编辑服务覆盖文件,加一行环境变量 sudo systemctl edit ollama.service # 在打开的编辑器里写入(注意是覆盖片段,不是整个 unit 文件) [Service] Environment="OLLAMA_HOST=0.0.0.0:11434" Environment="OLLAMA_MODELS=/data/ollama/models" # 生效并重启 sudo systemctl daemon-reload sudo systemctl restart ollama

macOS 用launchctl setenv OLLAMA_HOST "0.0.0.0",但这招重启后失效,长期方案是写 LaunchAgent 的 plist。Windows 直接在系统环境变量里加,然后退出托盘图标重开。这三个平台的处理方式不一样,是后面容器连不上、手机连不上的根源,第 4 章和第 5 章还会回到这一点。

2.3 模型规格怎么选:一张显存与内存的对照表

DeepSeek-R1 在 Ollama 上的蒸馏版本有好几档,命名上有个坑:7b和8b不是同一个底座。7b 蒸馏自 Qwen2.5,8b 蒸馏自 Llama 3.1,两者参数量接近但词表和对话模板不同,同一个提示词出来的风格会有差别。选哪个不是看数字大小,而是看你后续要不要接中文润色类任务——Qwen 底座在中文表达上更稳一些,Llama 底座在英文指令跟随上更利落。

下面这张表是我在自己机器和几台借来的机器上实测后整理的,体积是 Q4_K_M 量化下的大致下载量,内存栏是“能跑起来且不至于卡死”的下限,不是官方推荐值:

模型标签底座来源下载体积纯 CPU 最低内存建议显存(GPU 全量卸载)适用场景
deepseek-r1:1.5bQwen2.5约 1.1GB4GB2GB手机、树莓派、接口联调
deepseek-r1:7bQwen2.5约 4.7GB10GB6GB笔记本日常问答、代码补全
deepseek-r1:8bLlama 3.1约 4.9GB10GB6GB英文写作、指令跟随
deepseek-r1:14bQwen2.5约 9GB18GB12GB长文档摘要、中等复杂度推理
deepseek-r1:32bQwen2.5约 20GB32GB24GB复杂推理,需工作站
deepseek-r1:70bLlama 3.1约 43GB64GB48GB多卡或大内存服务器

几个判断经验。第一,纯 CPU 推理时,“最低内存”要留出余量给 KV cache 和操作系统,表里的数字已经含了一部分余量,但如果你同时开着浏览器和 IDE,7b 在 16G 机器上会明显吃力,8G 机器基本别想。第二,GPU 卸载不是全有全无,Ollama 支持部分层卸载,ollama run时会自动判断,你也可以在 Modelfile 里用PARAMETER num_gpu手动控制层数,层数调低会变慢但不会崩。第三,1.5b这一档特别容易被低估——它在手机和低配机器上是唯一现实的选择,虽然推理能力有限,但拿来做格式转换、关键词抽取、接口连通性验证完全够用。

拉多个规格共存的成本也要算进去。7b、8b、14b 全下一遍接近 19GB,模型目录在系统盘时很容易把盘塞满。我一般只保留当前在用的两个,其余用ollama rm清掉,需要时再拉——反正拉取有缓存层,重复拉同一版本不会重下全部。

2.4 把对话搬进代码:generate 与 OpenAI 兼容接口

终端里聊天只是验证,真正要用起来得走接口。Ollama 提供两套:原生的/api/generate和/api/chat,以及一套 OpenAI 兼容的/v1/chat/completions。原生接口的好处是能拿到thinking字段(R1 系列的思维链单独返回,不混在正文里),兼容接口的好处是你现有的 OpenAI SDK 代码一行不用改,只换 base_url。

# 原生接口,非流式,一次拿完整结果 # stream=false 是关键,默认是流式,脚本里不关会拿到一串 NDJSON curl http://localhost:11434/api/generate -d '{ "model": "deepseek-r1:7b", "prompt": "用三句话说明什么是幂等性", "stream": false, "options": { "temperature": 0.6, "num_ctx": 8192 } }'

options里的参数直接影响行为的几个:temperature默认 0.8,R1 这类推理模型建议压到 0.5~0.7,太高思维链会发散;num_ctx是上下文窗口,默认 2048,太小会导致长文档被截断而你不自知——这是最隐蔽的坑之一,模型不会报错,只是“忘事”;num_predict控制最大输出长度,R1 的思维链很长,默认值可能在中途被砍断,接业务时建议显式调大。

Python 侧走兼容接口更省事:

from openai import OpenAI # api_key 随便填,Ollama 不校验,但 SDK 要求非空 client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama") resp = client.chat.completions.create( model="deepseek-r1:7b", messages=[ {"role": "system", "content": "你是严谨的技术审校,回答控制在 200 字内"}, {"role": "user", "content": "解释一下 Docker 的卷挂载和绑定挂载的区别"}, ], temperature=0.6, timeout=120, # CPU 推理首 token 可能等很久,超时别设太小 ) print(resp.choices[0].message.content)

这段代码里有两点值得单独拎出来。一是timeout。纯 CPU 跑 7b,输入一段两千字的文档,首 token 等三十秒是常态,如果用默认的 60 秒超时,会在模型还在算的时候就被客户端掐断,报的错还像是网络问题。二是base_url末尾的/v1不能少,少了会走到 Ollama 原生的路由上,返回格式对不上,SDK 解析时抛的异常跟参数错误长得一样,很容易误判成模型名写错。

原生接口还有个用法是流式输出,stream=true时返回的是一行一个 JSON,每行有response字段和最后的done。做命令行工具或者网页打字机效果时走这个,用户感知的等待时间会短很多。不过要注意 NDJSON 的解析要按行缓冲,不能按块读,网络抖动时一个 JSON 可能被拆成两个 TCP 包,按块解析就会拿到半截。

3. 手机端两条路:快捷指令当客户端,Termux 当编译机

3.1 iPhone 快捷指令的本质:一个带鉴权的 HTTP 客户端

先说结论:iPhone 上的“DeepSeek 部署”绝大多数不是本地推理,而是把手机做成一个远程客户端。真正在跑的模型在别处——要么是你局域网里的那台 Ollama 主机,要么是服务方提供的 API。快捷指令在这里的角色,就是一个把“输入文本 → 发 POST → 展示返回”串起来的可视化 HTTP 客户端,没有任何魔法。

这个认知很重要,因为它决定了你该往哪儿排查问题。很多人装完快捷指令点了没反应,第一反应是“快捷指令坏了”,实际上九成是 URL 写错或者鉴权头没带。

配置流程大致是这样:拿到一个可用的 API 端点和对应的 Key,然后在快捷指令里加“文本”动作存 Key,加“获取 URL 内容”动作,方法选 POST,请求头加Authorization: Bearer <Key>和Content-Type: application/json,请求体用 JSON 结构写model和messages两个字段。如果你接的是自己局域网里的 Ollama,端点就是http://<内网IP>:11434/v1/chat/completions,Key 填任意非空字符串即可。

这里有几个必须注意的点。第一,端点不能写localhost或127.0.0.1,那指向手机自己,而手机上并没有服务。要用宿主机的局域网 IP,并且确认宿主机 Ollama 已经监听0.0.0.0。第二,iOS 对明文 HTTP 有额外限制,快捷指令里访问非 HTTPS 地址通常能用,但如果中间经过了反向代理,记得把证书配好,否则会静默失败。第三,请求体里的 JSON 要注意引号转义,快捷指令的文本替换和变量拼接很容易把中文引号带进去,导致服务端返回 400 而手机端只显示“无法连接”。

测试顺序我一般这么走:先用电脑上的curl打同一个端点,确认返回 200;再把同一段 JSON 贴进快捷指令;最后才做变量替换。跳过前两步直接拼,出问题时你分不清是服务端的问题还是手机端的问题。

如果只是想在手机上用,其实有个更省事的方案:把 Open WebUI 部署在局域网某台机器上(第 4 章会讲),手机浏览器直接访问那个 IP。快捷指令的价值在于“一句话唤起”,适合高频重复的固定任务,比如把剪贴板内容丢给模型做摘要。两种方式不冲突,我机器上两个都留着。

3.2 Android Termux 里从源码编译 Ollama:命令逐条修正

Android 这条路和 iPhone 完全不是一个性质——Termux 是个真终端,理论上可以在手机上本地跑模型。但“理论”和“实际”之间隔着一堆坑,我把原始流程和修正后的命令放在一起对照,你照着改就行。

先装环境。Termux 不要从应用商店装,商店版本长期不更新,包管理会出各种依赖冲突,用官方 release 的 APK。装完第一件事是给存储权限和更新索引:

# 申请存储权限,注意命令是 termux-setup-storage,中间是连字符 termux-setup-storage # 更新包索引并升级已有包,这一步在国网络环境下可能要等几分钟 pkg update && pkg upgrade -y # 装编译依赖。原流程里的 libjpegturbo 是错的,正确包名带连字符 pkg install -y git cmake golang libjpeg-turbo

依赖装完就开始拉源码。原流程里写的git clone depth 1少了两个连字符,正确的写法是--depth 1,加这个参数只拉最新一次提交,能把仓库体积从几百兆压到几十兆,手机流量和存储都省。后面的go generate./...少了空格,go build.少了空格且漏了输出参数,全都会直接报错:

# 浅克隆,只取最新提交 git clone --depth 1 https://github.com/ollama/ollama.git cd ollama # 生成嵌入式资源(模板、图标等),注意 generate 和路径之间有空格 go generate ./... # 编译,-o 指定输出文件名,方便后面直接调用 go build -o ollama .

编译这一步是整条链路最容易翻车的地方。Ollama 的 Go 代码里嵌了 CGO 依赖,编译峰值内存能到 2GB 以上,手机上如果同时开着微信和浏览器,很容易被系统 OOM killer 干掉,表现是编译到一半进程凭空消失、没有任何错误输出。我的做法是先pkg install -y zram之类的内存压缩工具意义不大,更实际的是:关掉后台应用、插上电源、把终端放在前台别切走,然后重试。如果连续两次失败,直接放弃源码编译,去 release 页下linux-arm64的预编译包,chmod +x之后一样能用——这条捷径能省掉半小时。

编译成功后启动服务和验证:

# 后台启动服务,& 让它不占住当前终端 ./ollama serve & # 验证:返回版本信息说明服务在跑 curl http://localhost:11434 # 拉模型。注意原流程写的 deepseekr1 少了连字符,正确的是 deepseek-r1 ./ollama pull deepseek-r1:1.5b # 运行,手机上别碰 7b 以上,8G 内存会直接 OOM ./ollama run deepseek-r1:1.5b

模型规格的选择在手机上要更保守。1.5b的 Q4 量化约 1.1GB,加上运行时开销,8G 内存的手机勉强够;7b需要 10GB 以上可用内存,绝大多数手机连启动都过不去。即使 1.5b 跑起来了,速度也就是每秒几个 token,做短问答可以,长文生成要有心理准备。

3.3 手机侧能跑多快:参数、散热与体验取舍

真在手机上跑起来之后,你会发现瓶颈完全不在模型,而在散热和调度。手机 SoC 的持续功耗墙很低,跑满两分钟就开始降频,速度断崖式下跌。我实测过同一台机器上 1.5b 的表现:前 20 个 token 大约每秒 6~8 个,三分钟后掉到 2~3 个,机身背面明显烫手。

能调的参数不多,但有两个有用。一是num_ctx,手机上默认 2048 就够,调大到 8192 会让 KV cache 膨胀,直接挤爆内存。二是num_thread,设成手机的大核数量(一般是 4 或 6),设太大反而因为小核参与调度而变慢。这两个都可以在 Modelfile 里写死,省得每次手输:

# 基于已有模型派生一个手机专用版本 cat > Modelfile <<'EOF' FROM deepseek-r1:1.5b PARAMETER num_ctx 2048 PARAMETER num_thread 4 PARAMETER temperature 0.6 EOF # 创建自定义标签 ./ollama create deepseek-mobile -f Modelfile # 之后直接跑这个标签 ./ollama run deepseek-mobile

这个 Modelfile 的写法在桌面端同样适用,是我比较推荐的习惯——把调好的参数固化成标签,而不是每次在命令行里敲一长串options。FROM指向的可以是官方标签,也可以是本地已有的自定义标签,能一层层叠加。

最后说一句实话:手机本地跑大模型,目前的实用价值主要在“离线可用”和“隐私不出设备”这两个点上,性能和体验都别抱太高期待。如果你的真实诉求是“随时随地能用”,那 3.1 的远程客户端方案才是正解,手机只负责输入输出,算力留在有显卡的机器上。

4. Docker 加 Open WebUI:端口、卷与容器内外的连接关系

4.1 为什么需要 UI 层

Ollama 自带的终端 REPL 能满足“问一句答一句”,但它缺三样东西:历史会话管理、多模型对比、以及给非技术同事用的界面。Open WebUI 补的正是这三块——它是个纯前端的壳,后端还是打 Ollama 的接口,自己基本不做推理。理解了这层关系,后面所有的连接问题都能推出来:容器里的 Open WebUI 要访问宿主机上的 Ollama,跨了网络命名空间,localhost就不再指同一台机器了。

Docker Desktop 在这条链路里只是个运行时。macOS 和 Windows 上它跑在一个轻量虚拟机里,Linux 上直接跑在宿主内核上,这个差别会影响网络行为,也是后面host.docker.internal有时能用有时不能用的原因。

4.2 docker run 逐段拆解

先给命令,再逐段解释,因为网上流传的版本参数差异很大,照抄容易踩坑。

docker run -d \ -p 3000:8080 \ --add-host=host.docker.internal:host-gateway \ -v open-webui:/app/backend/data \ --name open-webui \ --restart always \ -e OLLAMA_BASE_URL=http://host.docker.internal:11434 \ ghcr.io/open-webui/open-webui:main

-p 3000:8080是端口映射,左边是你浏览器访问的端口,右边是容器内 Open WebUI 实际监听的端口。左边可以随便改,3000 被占用就换 3001;右边千万别改,改了容器里的服务还是听 8080,映射对不上就是一片空白页。

--add-host=host.docker.internal:host-gateway这行是关键。Linux 上的 Docker 默认不认识host.docker.internal这个主机名(Docker Desktop 会自动加,Linux 原生不会),加上这行才会把它解析到宿主机的网关地址。少了它,容器启动正常,但连 Ollama 时一直超时。

-v open-webui:/app/backend/data是命名卷挂载,把会话记录、用户配置、上传的文档都落在卷里。不挂这个卷的话,docker rm一次所有对话记录全没,这个后悔药没处买。

-e OLLAMA_BASE_URL是告诉 Open WebUI 去哪儿找 Ollama。新版 Open WebUI 支持自动发现,但显式写死更稳,尤其是你宿主机上跑了多个 Ollama 实例的时候。

镜像我选的是:main,这是 CPU 版。有 N 卡并且装了 nvidia-container-toolkit 的话换成:cuda,推理会快很多;还有个:ollama标签是“自带 Ollama 的一体包”,省事但会把两个服务的生命周期绑死,模型和 WebUI 的数据混在一个容器里,我不太推荐。首次拉镜像大概 1~2GB,取决于标签。

启动之后用docker logs -f open-webui看日志,看到类似Uvicorn running on http://0.0.0.0:8080就说明服务起来了。第一次打开页面会要求注册管理员账号,这个账号只存在本地卷里,跟任何外部服务无关。

4.3 让 Open WebUI 认出已有的 Ollama

容器跑起来不等于连通。最常见的现象是页面能打开、能登录,但模型下拉框是空的,或者点开对话报“连接失败”。按这个顺序查:

第一步,确认宿主机 Ollama 在监听0.0.0.0。默认的127.0.0.1只接受本机回环连接,容器从网关地址进来是被拒的。这就是 2.2 里改OLLAMA_HOST的原因,很多人跳过了那一步,到这儿卡住。

第二步,在宿主机上确认端口确实开了:

# 看 11434 的监听地址,应该是 0.0.0.0 或 *,而不是 127.0.0.1 ss -tlnp | grep 11434 # 从宿主机网关地址访问一次,模拟容器的视角 curl http://172.17.0.1:11434

172.17.0.1是 Docker 默认 bridge 网络的网关,容器内看到的宿主机就是这个地址。如果这条命令在宿主机上都不通,容器里更不可能通。

第三步,进容器里验证:

# 进容器 docker exec -it open-webui bash # 容器内一般没有 curl,用 python 探一下更靠谱 python -c "import urllib.request;print(urllib.request.urlopen('http://host.docker.internal:11434').status)"

返回 200 说明网络通了,问题就在 Open WebUI 的配置上;返回超时说明网络层就没通,回去查--add-host和监听地址。这样分两段排查,比在页面上瞎点快得多。

还有一类情况是两个服务都正常但模型列表不刷新。Open WebUI 会把模型列表缓存在前端,Ollama 那边新拉了一个模型,页面上要手动点一下设置里的刷新,或者干脆重启容器。这个不算 bug,但足够让人怀疑自己拉取失败了。

4.4 多人与局域网访问的最小加固

默认配置下,能访问 3000 端口的人都能注册账号,这在局域网里就是敞开的。最小成本的处理方式是:先注册自己的管理员账号,然后进管理面板把“允许新用户注册”关掉,需要给人用时再手动开。Open WebUI 还支持按角色分配模型权限,把 70b 这种吃资源的模型只开给管理员,避免有人一口气发十个长文请求把机器拖死。

局域网访问还有个细节:容器映射到宿主机的 3000 端口默认对所有网卡开放,如果这台机器同时连着办公网和访客网,等于把界面暴露给了访客网。要么在宿主机防火墙上限制源地址段,要么把映射改成-p 127.0.0.1:3000:8080只允许本机访问,再通过一层反向代理对外。这两种做法都比“先跑起来再说”要稳妥。

5. 部署避坑与排查:五条翻车记录

5.1 模型拉取卡在 pulling manifest 不动

现象:执行ollama pull后进度条停在pulling manifest几分钟甚至更久,CPU 占用很低,看起来像卡死。

原因:这一步是在向分发端请求清单文件,网络链路质量差或者 DNS 解析慢时会长时间挂起。它并不像下载层那样有进度反馈,所以视觉上就是“没反应”。如果中途断网,进程不会自动重试,会一直等。

解决:先 Ctrl+C 中断,再用curl -I https://registry.ollama.ai确认端点可达;可达就重试,Ollama 对已下载的层有本地缓存,重试不会从头开始。如果反复失败,可以换一个时间段或者换个网络环境。另外要确认磁盘剩余空间足够——空间不足时它也会停在 manifest 阶段,报错信息藏在日志里,不翻日志根本看不到。

5.2 容器里连不上宿主机的 Ollama

现象:Open WebUI 页面正常打开,登录也正常,但模型下拉框空白,或者对话时提示连接被拒。

原因:九成是宿主机 Ollama 只监听127.0.0.1。容器的网络命名空间和宿主机是隔离的,容器内的localhost指向容器自己,访问宿主机得走网关地址,而回环地址上的服务不接受来自网关的连接。

解决:按 4.3 的三步走。先把OLLAMA_HOST改成0.0.0.0:11434并重启服务,再用ss -tlnp确认监听地址变了,最后进容器用 python 探一次。三个都通过还连不上,就去检查防火墙有没有拦 11434。

5.3 长文档问答答非所问,模型像失忆

现象:丢进去一份几千字的文档,问细节问题,模型答的内容和文档无关,或者只答前半部分的内容。

原因:num_ctx默认 2048,超出部分被静默截断。这个截断不报错、不警告,从接口返回看一切正常,只有对比原文才能发现。上下文长度是模型部署里最容易被忽略的参数,因为它的失败方式太安静了。

解决:显式把num_ctx调到 8192 或 16384,同时在应用层做文本切分,别指望一次塞进去。切分时按语义段落切比分固定长度切效果好,重叠区留 10%~15%。另外,调大num_ctx会线性增加内存占用,7b 模型在 8192 上下文下纯 CPU 推理需要的内存比默认值多出将近一倍,机器吃不消的话优先降上下文而不是降模型。

5.4 Termux 编译中途进程消失

现象:go build跑到一半直接返回 shell,没有报错,也没有生成可执行文件。

原因:Android 的内存管理比桌面激进,编译 Ollama 这类带 CGO 的项目峰值内存超过 2GB,系统判定内存压力大就把进程杀了。表现就是“凭空消失”,日志里什么都没有。

解决:编译前关掉所有后台应用,插上电源,终端保持前台。仍然失败就改用官方 release 的linux-arm64预编译包,下载后chmod +x直接运行,功能完全一样,省掉整个编译环节。我现在的习惯是手机上永远走预编译包,编译只在一台 32G 内存的机器上做。

5.5 端口冲突导致容器反复重启

现象:docker ps里容器状态不停在Restarting和Up之间切换,docker logs里有一行address already in use。

原因:宿主机的 3000 端口已经被别的服务占用(Node 项目的默认端口就是 3000,撞车概率很高)。Docker 在启动时绑定失败,容器退出,--restart always又把它拉起来,循环往复。

解决:ss -tlnp | grep 3000找出占用者,要么停掉它,要么把映射换成-p 3001:8080。改完记得docker rm -f open-webui再重新 run,直接docker restart不会重新解析端口映射。

6. 验收清单与压测小技巧:怎么确认这套部署真能用

部署完最怕的是“看起来能用”。页面上问一句“你好”能回,不代表接口、上下文、并发、持久化都正常。我给自己定了一套五步验收,每次换机器或者升级版本都走一遍,花不了十分钟,但能提前暴露大部分问题。

验收项操作通过标准
服务存活curl http://localhost:11434返回 Ollama is running
模型完整ollama list对比体积体积与官方标注接近,无截断
接口可用curl 打/v1/chat/completions200 且 JSON 结构完整
上下文生效输入 6000 字文档问结尾细节能答出结尾内容
持久化重启容器后翻历史会话会话记录仍在

第三步容易被忽略的是“用兼容接口而不是原生接口验证”。原生接口通了不代表/v1路由正常,而你后面的业务代码大概率走的是兼容接口。

第四步我一般用一个土办法压测:构造一段带随机数的长文本,让模型复述结尾的那个随机数。上下文不够长时它会编一个看起来合理的数字,这就是最直观的失败信号。比看日志可靠,因为截断根本不进日志。

# 上下文压测:把随机串埋在长文本末尾,看模型能不能捞出来 import random, string, requests needle = "".join(random.choices(string.ascii_uppercase + string.digits, k=12)) filler = "这是一段用于填充上下文的无关内容。" * 400 # 约 6000 字 prompt = f"{filler}\n\n请只回答文末的校验码:{needle}" r = requests.post("http://localhost:11434/api/generate", json={ "model": "deepseek-r1:7b", "prompt": prompt, "stream": False, "options": {"num_ctx": 8192, "temperature": 0.1}, }, timeout=300) print("期望:", needle) print("实得:", r.json()["response"].strip())

这段脚本的关键在于把temperature压到 0.1——压测验的是“有没有读到”,不是“答得好不好”,采样温度高了会引入随机性,反而干扰判断。timeout给到 300 秒是因为 CPU 推理长上下文确实慢,超时设小了会在模型还在算的时候断开。

一个进阶技巧是把num_ctx和num_predict分开压。前者影响输入能吃多长,后者影响输出能写多长。R1 系列的思维链会消耗大量输出 token,如果num_predict太小,模型在思维链中途被截断,返回的正文就是半截甚至空的,而接口状态码依然是 200。验证方法是看返回里的done_reason字段,如果是length就说明撞到上限了,得把值调大。

还有个和钱、和时间都有关的习惯:把OLLAMA_MODELS指到独立的数据盘,别让模型文件跟系统盘抢空间。我吃过一次亏,系统盘满了之后 Ollama 拉新模型失败,报的错是权限问题,查了半天才发现是磁盘写不进去。

从那以后我每次在新机器上部署,都强制先把这五步验收跑一遍,再往上接业务代码——尤其是“容器重启后会话还在不在”这一条,翻过一次车之后再也不敢跳过。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表