先说结论:内存是本地推理系统的第一瓶颈,也是 Agent 可靠性的核心
关键字是 “qmd memory”。它背后的真实诉求是:
- 你在用 QMD(本地推理/模型调度层)跑模型时,显存/内存不够用、OOM、性能突然掉。
- 你在用 OpenClaw 编排任务时,状态丢失、崩溃后无法恢复、内存泄漏导致进程卡死。
我的判断是:内存问题不是”优化问题”,而是”生存问题”。
如果你不把内存治理当成一等公民,你会遇到:
- 推理到一半 OOM 崩溃
- 任务状态丢失,重启后不知道从哪继续
- 内存泄漏导致系统越跑越慢
- 多任务并发时互相抢资源
本文按”能落地”的顺序写:先讲 QMD 推理层的显存/内存管理,再讲 OpenClaw 编排层的状态持久化与崩溃恢复。
1) QMD 推理层:显存是硬约束,内存是软约束
1.1 显存(VRAM):你的第一道红线
如果你用 GPU 跑本地模型(llama.cpp / vLLM / Ollama),显存是硬约束:
- 不够就 OOM
- 够了但不多,性能会因为频繁换页而掉
- 多了也不一定好(显存碎片、上下文窗口浪费)
最常见的坑:模型能加载,但推理时 OOM。
原因:
- 模型权重占一部分显存
- KV cache(上下文缓存)占一部分
- 中间激活值占一部分
- 并发请求会让 KV cache 线性增长
一个 7B 模型(FP16)大约需要:
- 权重:14GB
- KV cache(8K 上下文):~2GB
- 激活值:~1GB
- 总计:~17GB
如果你的显卡是 16GB(比如 RTX 4080),跑不动。
1.2 内存(RAM):CPU 模式的救命稻草
如果没有 GPU,或者显存不够,你会用 CPU 模式。 这时内存变成主战场:
- 模型权重加载到内存
- 推理过程在 CPU 上跑
- 速度慢 10~50 倍,但至少能跑
关键问题:内存够不够?
一个 7B 模型(FP16)需要 ~14GB 内存。 如果你的机器只有 8GB 内存,要么:
- 用量化模型(Q4/Q5,内存需求降到 4~6GB)
- 用 swap(但会非常慢)
- 换更小的模型
1.3 量化:用精度换内存
量化是本地推理的核心技术:
- FP16:原始精度,内存需求 = 模型参数 × 2 字节
- Q8:8-bit 量化,内存需求 = 模型参数 × 1 字节
- Q4:4-bit 量化,内存需求 = 模型参数 × 0.5 字节
一个 7B 模型:
- FP16:14GB
- Q8:7GB
- Q4:3.5GB
代价:精度下降。
- Q8:几乎无损
- Q4:轻微下降(对大多数任务可接受)
- Q2:明显下降(不推荐)
2) QMD 内存优化策略:从”能跑”到”能稳定跑”
2.1 策略一:限制上下文窗口(最有效)
上下文窗口越大,KV cache 占用越多。
如果你的任务不需要 32K 上下文,就别开那么大:
- 8K 上下文:KV cache ~2GB
- 16K 上下文:KV cache ~4GB
- 32K 上下文:KV cache ~8GB
实战建议:
- 写文章:8K 够用(分段生成)
- 代码补全:4K 够用
- 长文档问答:16K~32K
2.2 策略二:限制并发(避免显存/内存爆炸)
每个并发请求都会占用一份 KV cache。
如果你同时跑 10 个请求,KV cache 会乘以 10:
- 单请求:2GB
- 10 并发:20GB
解决方案:
- QMD 侧做队列,限制并发数(比如最多 2 个并发)
- OpenClaw 侧限制任务并发(比如写文章任务一次只跑 1 个)
2.3 策略三:动态卸载(GPU ↔ CPU)
如果显存不够,可以把部分层卸载到 CPU:
- llama.cpp 的
--n-gpu-layers参数 - 只把部分层放 GPU,其他放 CPU
这能让你在 8GB 显卡上跑 13B 模型(虽然慢一些)。
2.4 策略四:模型热切换(避免重复加载)
如果你有多个模型(writer-zh / coder / router),不要每次都重新加载。
正确做法:
- 常用模型常驻内存
- 冷门模型按需加载
- 用 LRU 策略淘汰
3) OpenClaw 编排层:状态持久化是可靠性的基石
QMD 管推理,OpenClaw 管编排。 编排层的内存问题不是”显存不够”,而是”状态丢失”。
3.1 为什么状态持久化这么重要?
Agent 系统的典型场景:
- 任务跑到一半,进程崩溃
- 服务器重启
- 网络中断
- 推理超时
如果没有状态持久化,你会:
- 不知道任务跑到哪了
- 不知道哪些步骤已完成
- 不知道产物在哪
- 只能从头重跑(浪费时间和资源)
3.2 OpenClaw 的内存架构:MEMORY.md + memory/*.md
OpenClaw 的设计哲学是:内存不是 RAM,而是文件。
核心文件:
MEMORY.md:索引 + 核心人物信息memory/active-tasks.md:崩溃恢复,当前任务memory/lessons.md:错误教训,每条只记一次memory/self-review.md:每 4 小时自我批评memory/projects.md:所有项目当前状态memory/YYYY-MM-DD.md:每日原始日志(永远不删除)
关键原则:启动时先读 active-tasks.md,自动恢复。
3.3 崩溃恢复协议:每个任务必须注册
在 active-tasks.md 里,每个任务必须有:
- 任务 ID
- 状态(in-progress / blocked / waiting)
- 开始时间
- 上下文(正在做什么)
- 下一步(具体要做什么)
- 超时时间(何时放弃)
示例:
## [astro-seo-qmd-memory] 生成文章
- **Status:** in-progress
- **Started:** 2026-02-15T02:54:34Z
- **Context:** 正在生成关键词"qmd memory"的文章
- **Next step:** 构建并发布到 gsccrelay.space
- **Files touched:** astro-site/src/content/blog/1771124502-qmd-memory.md
- **Timeout:** 2026-02-15T03:24:34Z(30 分钟)
崩溃后重启,Agent 读取这个文件,自动继续。
4) OpenClaw OS:用 SQLite 做状态机(比文件更可靠)
文件适合人类阅读,但不适合复杂状态管理。 OpenClaw OS 引入了 SQLite 作为状态存储:
4.1 核心表结构
tasks:任务表(id / workflow_id / state / dedupe_key / input_ref / output_ref)events:事件日志(task_id / event_type / payload_json / created_at)artifacts:产物表(id / task_id / type / uri / created_at)runs:执行实例(task_id / started_at / finished_at / status)
4.2 状态机:queued → running → done / failed
每个任务都有明确的状态:
queued:等待执行running:正在执行done:成功完成failed:失败blocked:阻塞(需要人工介入)
关键原则:状态先写,再执行。
错误示例:
# 先执行
result = generate_article()
# 再写状态
update_task_state('done')
正确示例:
# 先写状态
update_task_state('running')
# 再执行
result = generate_article()
# 成功后写 done
update_task_state('done', output_ref=result.url)
为什么?因为如果执行过程中崩溃,你至少知道任务在 running 状态,可以重试或标记失败。
4.3 事件日志:可追溯的审计链
每次状态变化都写 event:
INSERT INTO events (task_id, event_type, payload_json, created_at)
VALUES ('task-123', 'state_transition', '{"from":"queued","to":"running"}', NOW());
好处:
- 可以回溯任务历史
- 可以统计失败率
- 可以排查问题
5) 内存泄漏排查:Agent 系统的隐形杀手
5.1 常见泄漏点
- 上下文累积:多轮对话时,上下文越来越长,内存线性增长。
- 缓存不清理:文件缓存、HTTP 缓存、模型缓存不过期。
- 日志文件膨胀:日志写入不轮转,磁盘/内存被占满。
- 僵尸进程:子进程没有正确回收。
5.2 排查工具
top/htop:实时查看内存占用ps aux --sort=-%mem:按内存排序进程du -sh /path/to/logs:查看日志大小lsof -p <pid>:查看进程打开的文件
5.3 预防措施
- 限制上下文长度:超过阈值就截断或总结。
- 定期清理缓存:设置 TTL(Time To Live)。
- 日志轮转:用
logrotate或手动轮转。 - 进程监控:用 systemd 管理进程,自动重启。
6) 实战:一个完整的内存治理清单
6.1 QMD 推理层
- 选择合适的量化级别(Q4/Q8/FP16)
- 限制上下文窗口(8K/16K/32K)
- 限制并发数(1~3 个)
- 监控显存/内存占用(每分钟采样)
- 设置 OOM 告警(超过 90% 就告警)
- 配置 swap(但不要依赖它)
6.2 OpenClaw 编排层
- 每个任务注册到 active-tasks.md
- 状态先写,再执行
- 每次状态变化写 event
- 产物写入 artifacts
- 设置任务超时(避免永远 running)
- 定期清理旧日志(但保留 memory/*.md)
6.3 监控与告警
- 内存占用 > 80%:告警
- 任务 running 超过 30 分钟:告警
- 连续失败 3 次:熔断
- 磁盘占用 > 90%:告警
7) 常见问题与排错
7.1 推理时 OOM 怎么办?
- 检查显存/内存占用:
nvidia-smi/free -h - 降低上下文窗口
- 降低并发数
- 换更小的模型或更激进的量化
7.2 任务状态不一致怎么办?
- 检查
tasks表:SELECT * FROM tasks WHERE state='running' AND updated_at < NOW() - INTERVAL 30 MINUTE - 手动标记失败或重新排队
- 检查 events 日志,找到卡住的步骤
7.3 内存泄漏怎么排查?
- 用
top持续观察内存增长 - 检查日志文件大小:
du -sh /var/log - 检查缓存目录:
du -sh /tmp - 重启进程(临时方案)
- 找到泄漏点并修复(长期方案)
7.4 崩溃后如何恢复?
- 读取
active-tasks.md - 检查
tasks表中state='running'的任务 - 根据
next step继续执行 - 如果无法继续,标记
failed并记录原因
8) 高级话题:内存分层与冷热分离
8.1 内存分层
- 热数据:当前任务的上下文、模型权重(常驻内存)
- 温数据:最近 1 小时的任务状态(SQLite)
- 冷数据:历史日志、已完成任务(归档到磁盘)
8.2 冷热分离策略
- 热数据:内存 / GPU 显存
- 温数据:SQLite(内存模式或 SSD)
- 冷数据:压缩后存到对象存储(S3 / OSS)
好处:
- 降低内存压力
- 提高查询速度
- 降低存储成本
10) 配置示例:一个可复制的 QMD + OpenClaw 内存配置
10.1 QMD 配置(llama.cpp 示例)
# 启动 llama.cpp server
./server \
--model models/qwen2.5-7b-q4.gguf \
--ctx-size 8192 \
--n-gpu-layers 32 \
--parallel 2 \
--port 8000 \
--host 127.0.0.1
参数说明:
--ctx-size 8192:上下文窗口 8K(KV cache ~2GB)--n-gpu-layers 32:32 层放 GPU,其他放 CPU--parallel 2:最多 2 个并发请求--port 8000:监听端口
10.2 OpenClaw 配置(环境变量)
# QMD 配置
export QMD_BASE="http://127.0.0.1:8000"
export QMD_MODEL="writer-zh"
export QMD_TIMEOUT=120
# 内存限制
export MAX_CONTEXT_LENGTH=8192
export MAX_CONCURRENT_TASKS=1
# 状态持久化
export OS_DB_PATH="/home/admin/clawd/skills/astro-seo/openclaw-os.db"
export ACTIVE_TASKS_PATH="/home/admin/clawd/memory/active-tasks.md"
10.3 systemd 服务配置(自动重启)
[Unit]
Description=QMD Inference Server
After=network.target
[Service]
Type=simple
User=admin
WorkingDirectory=/home/admin/qmd
ExecStart=/home/admin/qmd/server --model models/qwen2.5-7b-q4.gguf --ctx-size 8192 --parallel 2
Restart=always
RestartSec=10
MemoryMax=16G
MemoryHigh=14G
[Install]
WantedBy=multi-user.target
关键配置:
Restart=always:崩溃自动重启MemoryMax=16G:硬限制 16GBMemoryHigh=14G:超过 14GB 开始回收
10.4 监控脚本(每分钟检查)
#!/bin/bash
# monitor-memory.sh
THRESHOLD=80
MEM_USAGE=$(free | grep Mem | awk '{print int($3/$2 * 100)}')
if [ $MEM_USAGE -gt $THRESHOLD ]; then
echo "[$(date)] ALERT: Memory usage $MEM_USAGE% > $THRESHOLD%" | tee -a /var/log/qmd-monitor.log
# 发送告警(Telegram/邮件)
curl -X POST "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID" \
-d "text=⚠️ QMD Memory Alert: $MEM_USAGE%"
fi
添加到 crontab:
* * * * * /home/admin/scripts/monitor-memory.sh
11) 真实案例:从 OOM 到稳定运行的完整路径
11.1 初始状态(问题重重)
- 模型:Qwen2.5-14B FP16(28GB)
- 硬件:16GB 显卡 + 32GB 内存
- 症状:推理 5 分钟后 OOM 崩溃
11.2 第一步:量化(立竿见影)
- 换成 Q4 量化:7GB
- 结果:能跑了,但并发 3 个请求时还是 OOM
11.3 第二步:限制并发(稳定性提升)
- 限制并发数:1
- 结果:稳定了,但吞吐量太低
11.4 第三步:降低上下文(平衡点)
- 从 32K 降到 8K
- KV cache 从 8GB 降到 2GB
- 结果:可以跑 2 个并发,稳定运行
11.5 第四步:状态持久化(可恢复)
- 每个任务注册到 active-tasks.md
- 状态写入 SQLite
- 结果:崩溃后能自动恢复
11.6 最终配置
- 模型:Qwen2.5-14B Q4(7GB)
- 上下文:8K(KV cache 2GB)
- 并发:2
- 内存占用:~12GB(稳定)
- 吞吐量:每小时 20 篇文章
12) 未来优化方向:从”能跑”到”跑得好”
12.1 模型蒸馏(降低内存需求)
- 用大模型生成训练数据
- 训练小模型(3B/7B)
- 部署小模型(内存需求降低 50%)
12.2 KV cache 压缩(提高并发)
- 用注意力压缩技术
- 减少 KV cache 占用
- 提高并发能力
12.3 分布式推理(突破单机限制)
- 多机部署 QMD
- OpenClaw 做负载均衡
- 水平扩展
12.4 混合云策略(成本与性能平衡)
- 本地模型处理 80% 任务
- 云模型处理 20% 复杂任务
- 成本降低 60%,可用性提升
9) 写在最后:内存治理是系统工程,不是性能优化
“qmd memory”不是一个性能优化话题,而是一个生存话题。
如果你不把内存治理当成一等公民,你会:
- 推理到一半崩溃
- 状态丢失无法恢复
- 内存泄漏导致系统越跑越慢
- 多任务并发时互相抢资源
正确的做法是:
- QMD 侧:量化、限制上下文、限制并发、监控显存
- OpenClaw 侧:状态持久化、事件日志、崩溃恢复、超时机制
- 监控侧:内存告警、任务超时告警、熔断机制
这才是”可运营的 Agent 系统”。
—— 完 ——