/root/.openclaw/workspace/tasks/task-embed-docker-e5c170/task.md/root/.openclaw/workspace/tasks/task-embed-docker-e5c170/session.md开发一个极简的 embedding 服务 Docker 代码库,部署在 RS1000(AMD EPYC Zen4),供 LE-B 上的 OpenClaw 调用。
LE-B(OpenClaw 主机) RS1000(embedding 服务器)
├── OpenClaw Gateway ├── embed-server(Docker)
├── Chromium(62GB RAM) │ └── Qwen3-Embedding-0.6B
└── memory_search ──────────────→ └── POST /v1/embeddings (+15ms RTT)
关键设计决策:
- embed server 只做向量生成,不做检索(检索由 OpenClaw 内置 sqlite-vec + BM25 hybrid 完成)
- 接口格式:OpenAI embeddings API 兼容(OpenClaw 原生支持)
- 模型从宿主机外部挂载(方便以后换更大的 Qwen3 模型)
- 镜像发布到线上 registry(ghcr.io 或 Docker Hub),方便迁移
- 监听 0.0.0.0:8765(需要从 LE-B 跨机访问,不能只绑 127.0.0.1)
运行环境(RS1000):
- CPU:AMD EPYC 9634,4 vCPU 虚拟机
- 支持:AVX-512 + AVX-512 BF16 + VNNI
- 内存:7.8GB 总计,可用约 5GB
最优推理配置(实测):
torch==2.7.0+cpu(纯 CPU 构建,非 CUDA build)
zentorch==5.1.0
sentence-transformers
OMP_NUM_THREADS=4
batch_size=8(建索引用)
推理代码(关键部分):
import torch
import zentorch # noqa: F401 — 注册 zentorch backend
torch.set_num_threads(4)
from sentence_transformers import SentenceTransformer
model = SentenceTransformer(os.environ.get("MODEL_PATH", "/models/Qwen3-Embedding-0.6B"))
model[0].auto_model = torch.compile(
model[0].auto_model,
backend=zentorch.zentorch_compiler_noinductor # 关键:绕过 inductor,走 ZENDNN/BLIS
)
# 启动时预热,吃掉 ~8s 编译时间,之后 healthcheck 才返回 ok
model.encode(["warmup"], normalize_embeddings=True)
为什么这样写(避坑):
- torch.compile(backend="inductor") → AVX-512 codegen bug,崩溃
- torch.compile(backend="zentorch") → 底层仍走 inductor,同样崩溃
- zentorch.optimize(model) → Qwen3 动态控制流无法 FX trace,报错
- ✅ 只有 backend=zentorch.zentorch_compiler_noinductor 有效
- PyTorch 必须用 2.7.0+cpu(CUDA build 在 CPU 模式有初始化开销,慢 2x)
- BF16 不推荐:单条短文本场景 cast overhead 抵消收益,反而慢 21%
性能基准:
- 单条 encode P50:40ms,P95:43ms
- 批量吞吐:25.5 条/秒(batch=8)
- 首次 zentorch 编译:~8s(一次性)
必须实现(OpenClaw 调用格式):
POST /v1/embeddings
Content-Type: application/json
{
"model": "Qwen3-Embedding-0.6B", // 会原样传入,建议忽略,用挂载的模型
"input": ["text1", "text2", ...] // 字符串数组,单条查询也是数组
}
响应:
{
"data": [
{"embedding": [0.123, -0.456, ...]}, // float 数组,dim=1024
{"embedding": [0.789, 0.012, ...]}
]
}
辅助端点:
GET /health
响应:{"status": "ok", "model_ready": true}
// 注意:模型预热完成前返回 {"status": "loading", "model_ready": false}
OpenClaw 配置(供参考,不需要实现):
agents.defaults.memorySearch: {
"provider": "openai",
"model": "Qwen3-Embedding-0.6B",
"remote": {
"baseUrl": "http://RS1000_IP:8765/v1",
"apiKey": ""
}
}
embed-server/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── server.py # FastAPI 主程序
├── requirements.txt
└── README.md # 部署说明
python:3.11-slimpip install torch==2.7.0 --index-url https://download.pytorch.org/whl/cpuzentorch==5.1.0 sentence-transformers fastapi uvicornOMP_NUM_THREADS=4 环境变量uvicorn server:app --host 0.0.0.0 --port 8765embed-server8765:8765(对外开放,供 LE-B 访问)${MODELS_DIR:-/home/aichan/models}:/models:ro(模型目录,只读)MODEL_PATH=/models/Qwen3-Embedding-0.6B(可覆盖换模型)alwaysGET /health,启动等待 30s,间隔 30s,超时 10s,重试 3 次4g(防止 OOM 影响其他服务)model_ready = True/v1/embeddings:接受 {"model": str, "input": list[str]},返回标准格式/health:返回 ready 状态列出所有依赖及版本(torch 单独注释说明安装方式,不能通过 requirements.txt 直接安装 cpu-only 版)
包含:
1. 快速部署步骤(3步:clone → 放模型 → docker compose up)
2. 模型下载命令(huggingface-cli)
3. 换更大模型的方法(改 MODEL_PATH)
4. OpenClaw 配置示例
5. 测试命令(curl 测试 /health 和 /v1/embeddings)
6. 性能参数说明(RS1000 实测数据)
coder → reviewer → 爱衣质检
agent:coder:main)任务:按上述规格实现完整代码库,输出到 /root/.openclaw/workspace-coder/tasks/task-embed-docker-e5c170/
开始时:
1. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh coder receive "Embed Server Docker 开发" task-embed-docker-e5c170
完成后:
1. 将实现说明和关键决策追加到 session.md(/root/.openclaw/workspace/tasks/task-embed-docker-e5c170/session.md)
2. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh coder handoff "Embed Server Docker 开发" reviewer task-embed-docker-e5c170
3. sessions_send 通知 reviewer(agent:reviewer:main,必须传 timeoutSeconds=0,禁止省略):
task_id=task-embed-docker-e5c170
task=/root/.openclaw/workspace/tasks/task-embed-docker-e5c170/task.md
code_path=/root/.openclaw/workspace-coder/tasks/task-embed-docker-e5c170/
agent:reviewer:main)任务:审查 coder 产出的代码库,重点检查:
1. zentorch_compiler_noinductor 使用方式是否正确(参考 task.md 避坑说明)
2. /v1/embeddings 响应格式是否符合 OpenClaw 期望
3. 预热逻辑是否在 healthcheck 前完成
4. Dockerfile layer 顺序是否合理(torch 单独一层)
5. docker-compose 内存限制、healthcheck 配置是否合理
6. README 部署步骤是否清晰可执行
开始时:
1. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh reviewer receive "Embed Server Docker 开发" task-embed-docker-e5c170
完成后:
1. 将审查报告追加到 session.md
2. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh reviewer handoff "Embed Server Docker 开发" main task-embed-docker-e5c170
3. sessions_send 通知爱衣(agent:main:main,必须传 timeoutSeconds=0,禁止省略):
task_id=task-embed-docker-e5c170
task=/root/.openclaw/workspace/tasks/task-embed-docker-e5c170/task.md
du -sb /root/.openclaw/workspace/tasks/task-embed-docker-e5c170/
wc -l /root/.openclaw/workspace/tasks/task-embed-docker-e5c170/session.md
通过 →
1. 将代码库复制到正式路径:
bash
cp -r /root/.openclaw/workspace-coder/tasks/task-embed-docker-e5c170/ \
/root/.openclaw/workspace/projects/embed-server/
2. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh main done "Embed Server Docker 开发" task-embed-docker-e5c170
3. message 主人(telegram, 92763607),附代码库路径和关键内容摘要
不通过(rejectCount == 0) →
1. 在 session.md 末尾追加 rejectCount=1
2. sessions_send 给 coder 说明问题,重新开发
3. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh main retry "Embed Server Docker 开发" "coder → reviewer → main" coder 1 task-embed-docker-e5c170
rejectCount >= 1 →
1. 发工作日志:
bash
/root/.openclaw/workspace/scripts/log-to-channel.sh main fail "Embed Server Docker 开发" task-embed-docker-e5c170
2. message 主人请裁决