如果你也折腾过本地大模型,应该能理解"第一次让模型自主干活"的那种兴奋。我花了一个周末,用 Ollama 把一套被社区叫做 GLM-5.1 的国产开源模型部署到本地,写了一个任务循环,让它连续跑了 8 小时,自动读取任务文件、生成分析结果、写回报表,全程没有人工干预。这篇文章不是简单的"一行命令装模型",而是把"部署—定制—编排—跑通"整条链路完整拆开,包括我踩过的显存炸裂、上下文失忆、API 超时等一堆坑。
想对照着复现的读者,建议具备最基础的命令行操作能力,懂一点 Python 更好。整个过程可以概括成四件事:搞清楚模型为什么适合自主任务、把 Ollama 部署干净、定制一个专门干活的模型配置、再写一个带兜底的重试循环。把这四件事做完,"8 小时自主 AI 工作"就不再是宣传语,而是你真正能跑起来的生产工具。
1. GLM-5.1 凭什么能扛"8 小时自主工作"?
1.1 长上下文与指令遵循:自主任务的基础能力
先说结论:不是所有开源模型都适合丢进自主循环。所谓"自主 AI 工作",本质是让模型在无人盯守的情况下,连续处理一批又一批子任务。这意味着每批任务之间,模型要能记住自己的角色、目标、格式要求,不能上一批跑完就开始胡说。
我最早拿一个 7B 量级的旧模型试过,跑了不到半小时就出问题:系统提示被上下文冲淡,输出格式从 JSON 慢慢变成了散文,最后干脆开始编造不存在的字段。换成 GLM-5.1 后,同样一个任务循环,连续处理几百个小任务,输出格式依然稳定。它的指令遵循能力明显更强,而且中文本地化的表达更自然,不会在批量摘要里突然冒出一段翻译腔。
这个模型的官方上下文窗口参数我没法拉满跑,因为显存有限,但在我自己配置的 8192 上下文下,它依然能维持"角色—任务—约束"三合一的稳定记忆。对于自主循环里最常见的"读入一段输入、按固定 schema 输出",这个能力已经足够硬。
1.2 工具调用能力:模型负责决策,代码负责执行
自主工作的第二层能力,是模型能不能给出结构化的动作指令。我在脚本里设计了这样一个模式:模型不直接操作文件系统,而是输出一个 JSON,比如"我要调用 write_file 函数,参数是路径和内容",再由外部 Python 代码真正执行。
GLM-5.1 对这类函数调用的输出格式掌握得比我预期好。给它一个函数清单,它基本不会把参数名写错。这意味着我可以构建一个安全的"工具舱",让模型只通过白名单函数操作文件、调接口、跑查询。其他开源模型经常在这个环节翻车,输出一些似是而非的 JSON,导致解析失败,而它给我的体感是"基本不需要修正 schema"。
1.3 "8小时"不是模型连续烧脑,而是编排循环在持续驱动
很多人误解"自主 AI 工作",以为是让模型一口气生成 8 小时内容。实际不是。模型单次推理的时间窗口很有限,通常是几十秒到几分钟。真正的 8 小时,是外部编排程序把一个大任务拆成无数个小请求,逐个喂给模型,再把结果收集起来。
所以"能不能跑 8 小时",三分看模型能力,七分看你的代码能不能扛住长时间调度。模型会中断吗?大概率不会。但你的循环、文件读写、异常处理、上下文管理任何一个环节出问题,整个流程就会提前死掉。后面几章,我把这些环节分别讲清楚。
2. 选 Ollama 部署的底层逻辑与硬件前置准备
2.1 为什么我不用纯 Python 推理框架,而是选 Ollama
第一次部署时,我尝试过直接用 Python 加载模型权重来做推理。这条路不是说走不通,而是太绕:你得手动处理分词器、显存分配、缓存管理,还要自己写一个可以被外部访问的服务接口。对于"快速验证一个自主任务"来说,启动成本有点高。
Ollama 最香的地方在于它把推理服务封装成了现成的 HTTP API。我只要一行命令启动服务,然后任何机器上的程序都能通过localhost:11434调用模型。它底层帮你处理了模型权重的加载、GPU 内存的分配、请求队列的管理。对写业务逻辑的人来说,这一步算是不用重复造轮子。
另外 Ollama 的模型管理非常友好。不同模型、不同量化版本,通过ollama pull一键下载,用标签区分。我要在同一个电脑上跑多个模型做对比,也只是多几条命令的事。它甚至支持Modelfile自定义系统提示和参数,这刚好符合我后面"定制自主任务专用模型"的需求。
2.2 硬件配置清单:最低下限、推荐上限
本地跑 GLM-5.1 到底需要什么配置?我列一张表,是我实测后的体感阈值,不是官方要求。
| 配置项 | 最低下限 | 推荐配置 | 说明 |
|---|---|---|---|
| CPU | 8 核以上 | 16 核以上 | 纯 CPU 模式也能跑,但速度慢到崩溃 |
| 内存 | 16 GB | 32 GB 及以上 | 加系统缓存和模型映射 |
| GPU 显存 | 8 GB | 24 GB 及以上 | 决定你是否能用更大上下文 |
| 磁盘空闲 | 30 GB | 80 GB SSD | 模型文件通常 5-30 GB 不等 |
| 网络 | 不稳定能续传 | 稳定即可 | 首次拉取模型体积大,断点续传很重要 |
如果你的显卡只有 8 GB 显存,建议选择量化版本,并且把num_ctx控制在 4096 以下,不然推理到一半就会显存溢出。我自己的环境是 16 GB 显存,用的是中等参数量模型加 8192 上下文,跑 8 小时过程中显存占用稳定在 80% 附近。
2.3 安装前的系统准备和隐藏依赖
Ollama 的安装命令很简单:
curl -fsSL https://ollama.com/install.sh | sh但"一键"之前有几个容易被忽略的坑。第一,它默认监听 11434 端口,你得保证这个端口没被别的服务占掉。第二,如果你在服务器上跑,建议直接用systemctl status ollama确认服务已经常驻,而不是每次手动开一个终端。第三,下载模型需要稳定的网络环境,有些网络下模型文件拉一半就断,Ollama 支持续传,但偶尔会卡住,遇到这种情况直接ollama rm删除残留标签再重新拉。
macOS 和 Windows 用户可以直接装官方桌面版,图形界面里点一下就能把服务跑起来。但考虑到 8 小时自主任务需要在后台稳定运行,我还是更推荐 Linux 服务器加systemd托管的方式,至少不会出现合上笔记本盖子就断开这种意外。
3. Ollama 拉取 GLM-5.1 的完整实操
3.1 拉取模型与本地文件管理
先装好 Ollama,然后用下面这条命令把模型拉到本地:
ollama pull glm5下载过程会显示多个层的下载进度。模型被存放在~/.ollama/models目录下,所以你不需要关心具体二进制文件在哪。
拉取完成后,用ollama list可以查看本地所有模型标签。我想强调一点:如果你跑 8 小时自主任务,一定要把模型名称写对。我习惯在脚本开头定义一个MODEL_NAME常量,因为脚本一旦跑起来,拼错一个字符,所有请求都会立刻失败。
日常调试时,可以直接命令行对话:
ollama run glm5 "用一句话解释什么是任务队列"这个命令会拉起来一个交互式终端,适合快速验证模型是否正常。不过正式跑任务时,我不会用这个命令,而是通过 HTTP API,方便脚本自动化。
3.2 用 HTTP API 完成第一次接口调用
在等待模型正常响应之后,可以直接用curl测接口:
curl http://localhost:11434/api/chat \ -d '{ "model": "glm5", "messages": [{"role": "user", "content": "1+1=?"}], "stream": false }'返回的 JSON 里有一个message.content字段,这就是模型生成的文本。stream设为false我在自主任务里使用,因为脚本更好处理一次性返回的完整内容。如果你做交互式聊天,可以设成true,效果就是打字机流式输出。
这个 API 协议与很多常见服务兼容。我后面写 Python 脚本时,甚至可以直接用 OpenAI 风格的客户端去访问,只要把base_url指到http://localhost:11434/v1就行。这是个极大的便利。
3.3 用 Modelfile 定制一个"自主任务专用模型"
直接跑默认模型也可以,但默认的系统提示是通用的,而自主任务通常要求模型"输出严格 JSON""不要附额外解释""你是数据分析助手"等。Ollama 提供了Modelfile,相当于给模型套一层自定义配置。
我创建了一个Modelfile,内容大概是这样:
FROM glm5 PARAMETER temperature 0.2 PARAMETER num_ctx 8192 PARAMETER top_p 0.9 SYSTEM """ 你是一个数据处理助手。你只允许输出 JSON 格式的结果,不要输出多余解释。 当任务无法完成时,输出 {"error": "原因说明"}。 在每次回应之前先确认你要调用的函数名称和参数。 """然后执行:
ollama create glm5-worker -f Modelfile之后我脚本里的模型名就写glm5-worker,而不是glm5。为什么要单独创建一个版本?因为默认模型的num_ctx可能只有 2048,自主任务里一旦输入稍微长一点,模型就会"失忆",这个我后面详细讲。定制一个自己的标签,随时改参数,也不会影响原始模型。
注意:
num_ctx设置得越大,显存占用越高。如果你是 8 GB 显存,建议先设 4096 跑通,再慢慢调大。宁可上下文短一点,也不要让它 OOM。
4. 把"一键部署"扩展成"8 小时自主工作"
4.1 自主工作循环的设计骨架
模型部署好之后,真正决定"8 小时"能跑起来的,是外围的这个循环。我的设计分四个模块:
- 任务来源:一个目录里有若干个 JSON 文件,每个文件是一个子任务。
- 模型调用层:封装 Ollama API,带超时和重试。
- 工具执行层:模型输出 JSON 指令后,由脚本安全地调用具体函数。
- 日志与熔断:每处理一个任务写一行日志;连续失败超过阈值,自动休眠一段时间。
这个设计的好处是,模型不直接接触系统,而是输出"我想做什么",由脚本决定是否允许。即使模型被 prompt 恶意引导,它也只能调用白名单函数,无法直接执行任意命令。
4.2 可复用的 Python 自主循环脚本
下面是一个简化但能跑的骨架脚本,我实际项目中就是在它的基础上增加的细节:
import json import os import time import requests OLLAMA_URL = "http://localhost:11434/api/chat" MODEL_NAME = "glm5-worker" TASK_DIR = "./tasks" RESULT_DIR = "./results" LOG_FILE = "./run.log" START_TIME = time.time() DURATION = 8 * 60 * 60 # 8小时 MAX_ATTEMPT = 3 def log(msg): line = f"[{time.time() - START_TIME:.0f}s] {msg}" print(line, flush=True) with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(line + "\n") def chat(messages, max_tokens=2048): payload = { "model": MODEL_NAME, "messages": messages, "stream": False, "options": {"num_predict": max_tokens, "temperature": 0.3}, } last_exc = None for attempt in range(5): try: resp = requests.post(OLLAMA_URL, json=payload, timeout=(10, 180)) resp.raise_for_status() return resp.json()["message"]["content"] except Exception as exc: last_exc = exc wait_time = min(2 ** attempt, 30) log(f"调用失败,{wait_time}s 后重试:{exc}") time.sleep(wait_time) raise RuntimeError(f"模型调用多次失败: {last_exc}") def process_task(task): task_id = task["id"] text = task["text"] messages = [ {"role": "system", "content": "你是GLM-5.1驱动的任务助手,只输出JSON。"}, {"role": "user", "content": f"任务ID: {task_id}\n内容: {text}\n请输出分类和摘要。"}, ] result_text = chat(messages) try: result = json.loads(result_text) except json.JSONDecodeError: # 模型可能输出了注释,要求它只输出JSON fallback = chat([ {"role": "system", "content": "你是修正助手,将上一轮响应转成合法JSON,不要加其他内容。"}, {"role": "user", "content": result_text}, ]) result = json.loads(fallback) return result def main(): log("自主工作循环启动") while time.time() - START_TIME < DURATION: pending = [f for f in os.listdir(TASK_DIR) if f.endswith(".json")] if not pending: log("暂无任务,等待 120 秒") time.sleep(120) continue task_file = pending[0] with open(os.path.join(TASK_DIR, task_file), encoding="utf-8") as f: task = json.load(f) if task.get("attempt", 0) >= MAX_ATTEMPT: log(f"跳过任务 {task['id']},超过最大尝试次数") os.rename(os.path.join(TASK_DIR, task_file), os.path.join(RESULT_DIR, f"skip_{task['id']}.json")) continue try: result = process_task(task) with open(os.path.join(RESULT_DIR, f"done_{task['id']}.json"), "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) os.remove(os.path.join(TASK_DIR, task_file)) log(f"完成任务 {task['id']}") except Exception as exc: task["attempt"] = task.get("attempt", 0) + 1 with open(os.path.join(TASK_DIR, task_file), "w", encoding="utf-8") as f: json.dump(task, f, ensure_ascii=False, indent=2) log(f"任务 {task['id']} 失败,第 {task['attempt']} 次:{exc}") # 小睡一下,避免连续打爆API time.sleep(1) log("达到 8 小时,停止循环") summary_task = { "id": "summary", "text": "统计今天所有任务完成情况,输出总结。", } # 最后让模型生成一份执行摘要 summary = process_task(summary_task) with open(os.path.join(RESULT_DIR, "summary.json"), "w", encoding="utf-8") as f: json.dump(summary, f, ensure_ascii=False, indent=2) if __name__ == "__main__": main()这个脚本最核心的就是"失败—重试—熔断"机制。任务文件里记录attempt次数,超过 3 次直接跳过并改名,避免同一个坏任务卡住整个循环。而DURATION就是"8 小时"的落点,程序到点自动写总结退出。
4.3 怎么往任务源里喂不同类型的任务
任务源目录里的每个 JSON 文件都可以具备不同字段,比如:
{ "id": "task_001", "type": "classification", "text": "用户反馈:充值到账延迟,客服态度差。" }我只要在process_task里根据type字段,构建不同的 prompt 和输出 schema,就能实现多任务混跑。比如一个 8 小时周期里,上午处理客服消息分类,下午处理商品评论摘要,晚上处理竞品舆情报告。这样的灵活性,靠的就是"任务源—模型调用—工具执行"三者的解耦。
4.4 时长控制与优雅退出
我额外强调一下优雅退出。你不想让循环写到一半就死,所以脚本每小时都会把当前进度写进state.json,任务完成一个删一个。这样就算系统意外重启,脚本再跑也能从现有任务目录里继续,而不是从头开始。8 小时这种长周期任务,断点续跑几乎是必须的。
5. 实战笔记:一次真实跑通 8 小时的自主任务
5.1 任务设定:批量竞品舆情分析
我在自己的服务器上模拟了一个任务负载:90 个 JSON 文件,每个文件包含 20 条用户评论,要求模型对每条评论做三件事:情感极性判断、核心主题抽取、一句话摘要。90 个文件总共 1800 条评论,如果靠人工,大概需要一个白天;我想让 GLM-5.1 在我睡觉的 8 小时里自己做完。
每个子任务的设计很清晰:输入一组评论,输出一个包含 20 条结构化结果的 JSON。批量处理时,1800 条评论被分层 90 个请求,每次请求约需要 50-100 秒左右,所以 8 小时窗口勉强够。
5.2 运行过程的四个阶段与数据
我把运行过程拆成四个阶段,方便观察:
| 时间段 | 状态 | 处理进度 | 资源占用 |
|---|---|---|---|
| 0-2 小时 | 启动稳定期 | 24 个文件 | 显存约 75% |
| 2-4 小时 | 高峰期 | 28 个文件 | 显存稳定,CPU 偶发 90% |
| 4-6 小时 | 等待期 | 22 个文件 | 出现几次 API 超时,自动重试 |
| 6-8 小时 | 收尾期 | 15 个文件 | 生成了总结报告,正常退出 |
第 4-5 小时出现过一次上下文堆积问题。我最初的版本把前一个任务的回答也拼接到下一个任务的上下文里,导致上下文越来越长,响应速度显著变慢。后面我把每个子任务改成完全独立的对话,只有系统指令保留,问题立刻缓解。
5.3 结果质量与资源占用观察
第二天早上醒来,我第一件事是随机抽了 50 条结果做人工检查。情感极性的准确率大概在 91%,核心主题的命中率稍低一些,约 85%。之所以有误差,是因为模型偶尔会把表达夸张的评论判定为负面,比如"这家店太绝了,居然还能再烂"这类反讽。
资源占用方面,16 GB 显存在 8192 上下文下会被占到 12-13 GB,剩下大约 3 GB 给系统和调度程序,整体稳定。但如果在跑的过程中再开一个图形界面程序,很容易把显存顶爆。所以真要跑 8 小时,建议把机器上的图形化工具全关掉。
提示:别把 8 小时看成硬性指标。如果任务少,模型提前跑完,我建议写一个"当任务清空后自动进入待机"的逻辑,而不是空转耗电。
6. 踩坑复盘:连续运行中的显存、上下文与卡死问题
6.1 上下文堆积导致模型"失忆"
这是我遇到的最大坑。最开始,我为了让模型记住全局目标,把 system 消息、历史对话、之前任务的结果全塞进 messages,结果跑了两个多小时,模型开始答非所问,甚至重复输出上一条任务的答案。
原因是num_ctx虽然设成了 8192,但几千条消息拼接下来,输入长度早就超过窗口。超过之后,Ollama 不是报错,而是会截断上半部分,系统提示恰好被截掉了,模型自然就开始"忘了自己是谁"。
解决方式很简单:每个子任务独立对话,上下文里只放系统提示+当前任务输入。如果确实需要跨任务记忆,我会在每个子任务开始前,让模型生成一段 100 字的"上阶段摘要",再放在系统提示里。摘要的信息密度远大于几十条原始历史消息。
6.2 显存不足与 OOM
自主任务和普通聊天不一样,它会连续不间断地推理,因此显存必须"跑长跑"而不是"短跑冲刺"。我有一次尝试把num_ctx从 8192 调到 16384,使得单次请求的 KV cache 暴增,跑了不到一小时,Ollama 直接崩溃,日志里有CUDA out of memory。
解决方式:
- 把上下文降到 8192 甚至 4096。
- 换用更低精度的量化版模型。
- 在 Ollama 环境变量里设置 GPU 层数上限,给系统留点显存。
- 24 小时定时重启一次服务,释放临时缓存。
如果你用的是 Windows 版,还需要额外注意集成显卡和独立显卡的切换问题,否则 Ollama 可能加载到你不想用的显卡上。
6.3 API 请求卡死、超时重试
有一次运行到第 5 个小时,整个循环停滞,日志显示一条请求已经挂了三分钟。后来发现是某条用户评论特别长,模型生成时间也跟着变长,而requests默认没有设置读取超时。
我修复的方法是在请求里指定timeout=(10, 180),10 秒是连接超时,180 秒是读取超时。超过 180 秒还没返回,就认为这次请求失败,触发重试。重试的时候我会退避等待,第一次等 2 秒,第二次等 4 秒,最多等 30 秒,避免在模型已经卡死时反复轰炸 API。
6.4 自主循环失控的兜底策略
所谓失控,一种表现是同一任务反复失败反复重试,永远不前进;另一种表现是模型开始输出大量无效 JSON,解析失败率上升。我的兜底策略是一个"连续失败熔断器":脚本记录最近 10 个任务的成功率,如果成功率低于 30%,就暂停 10 分钟,然后清理失败任务,只保留成功率高的任务类型。
这个设计非常管用。有一次模型因为某类 prompt 格式问题连续失败,熔断器介入后,我保住了一批已完成的结果,而不是让整个循环在白费力气。
7. 一些值得单独记住的经验
7.1 给模型一个"截止时间点"
在系统提示里加上"你剩余时间还剩 2 小时"这句话,会让模型输出更克制。它不会在一个问题上反复展开,而是更倾向于快速完成。我实测两种模式下的平均响应时间有 20% 左右差距。
7.2 定期生成摘要,而不是堆砌历史
如果一个"自主工作"任务需要跨子任务记忆,不要把所有历史都放在上下文里。更优的做法是每完成 N 个任务,调用模型生成一份"当前进展摘要",然后清空历史对话。摘要几十个字就能概括一大段结果,而且不会撑爆num_ctx。
7.3 安全边界:模型只发指令,代码才执行
最后再强调一次安全。真正跑 8 小时无人值守,你不可能盯着每次输出。所以我会在脚本里保证:模型永远只输出 JSON 指令,实际读写文件、调外部 API 的都是 Python 代码。程序里有一个全局ALLOWED_FUNCTIONS白名单,不在名单里的函数一概拒绝执行。这个设计让我敢在睡觉时让它自动写文件、改文件名、跑统计,而不是在提心吊胆中度过一个晚上。
7.4 Ollama 服务也需要守护进程
Ollama 默认服务在 Linux 下通过 systemd 管理,一般不会崩。但如果你用的是普通命令行启动,一旦 SSH 断开会话,服务可能跟着死掉。最简单的方式是nohup ollama serve > ollama.log 2>&1 &,再写一个 cron 每分钟探活,发现端口 11434 没响应就重新拉起。做这步之后,我的"8 小时"才真正变成"无人值守 8 小时"。
我自己跑通这一整套之后最大的感受是:开源模型的进度比很多人想象中要快。GLM-5.1 这类模型已经具备稳定输出结构化指令的能力,缺的只是外层这套"把它当员工使唤"的工作流。你现在拿这套 Ollama 部署加 Python 循环的底子,完全可以扩展到更复杂的自动化场景——比如定时舆情巡检、夜间批量数据处理、多模型交叉验证。只要把任务拆细、上下文管住、失败熔断做好,让模型替你值一个夜班,真的不是什么科幻故事。