先说结论:内存是本地推理系统的第一瓶颈,也是 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 常见泄漏点

  1. 上下文累积:多轮对话时,上下文越来越长,内存线性增长。
  2. 缓存不清理:文件缓存、HTTP 缓存、模型缓存不过期。
  3. 日志文件膨胀:日志写入不轮转,磁盘/内存被占满。
  4. 僵尸进程:子进程没有正确回收。

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 怎么办?

  1. 检查显存/内存占用:nvidia-smi / free -h
  2. 降低上下文窗口
  3. 降低并发数
  4. 换更小的模型或更激进的量化

7.2 任务状态不一致怎么办?

  1. 检查 tasks 表:SELECT * FROM tasks WHERE state='running' AND updated_at < NOW() - INTERVAL 30 MINUTE
  2. 手动标记失败或重新排队
  3. 检查 events 日志,找到卡住的步骤

7.3 内存泄漏怎么排查?

  1. 用 top 持续观察内存增长
  2. 检查日志文件大小:du -sh /var/log
  3. 检查缓存目录:du -sh /tmp
  4. 重启进程(临时方案)
  5. 找到泄漏点并修复(长期方案)

7.4 崩溃后如何恢复?

  1. 读取 active-tasks.md
  2. 检查 tasks 表中 state='running' 的任务
  3. 根据 next step 继续执行
  4. 如果无法继续,标记 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:硬限制 16GB
  • MemoryHigh=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 系统”。

—— 完 ——