auto-snapshot 2026-09-01 23:22:39

This commit is contained in:
小唯 A06 2026-09-01 23:22:40 +08:00
parent 7709fe1c56
commit 5d3b64de0c
21 changed files with 1457 additions and 207 deletions

View File

@ -77,13 +77,26 @@ curl -s -X POST -H "X-API-Key: zhiyi-...026" \
cd ~/.hermes/hermes-agent && python3 -c "from plugins.memory.zhiyi import HermesZhiYiMemoryProvider; p=HermesZhiYiMemoryProvider(); print(p.is_available(), len(p.get_tool_schemas()))"
```
### bge-embed 正常态4GB 显存笔记本2026-09-01 实测)
### bge-embed 正常态4GB 显存笔记本2026-09-01 v2 实测)
- **CUDAExecutionProvider 是正常态**bge-m3 设计上是 GPU 推理,占用 ~600MB 显存。看门狗必须要求 CUDA。
- **不要接受 CPU fallback**:如果 `/health` 返回 `CPUExecutionProvider`,说明 onnxruntime 装错了(应装 `onnxruntime-gpu`)或 CUDA 库路径不对
- **CUDA 13 库复用**:这台机器 ComfyUI venv 已装好 `nvidia/cu13` + `nvidia/cudnn`bge 通过 LD_LIBRARY_PATH 复用,不需要重装 CUDA toolkit
- **venv 路径**`/home/muc/.hermes/venvs/bge-embed/`(独立,**不污染** hermes 本体)
- 详见 skill: `bge-embed-crash-loop-fix`(根因 Cvenv 缺依赖 / 用错 onnxruntime
- **当前设计**bge 跑 CPUllama 7B 跑 GPU共享 4GB 显存)
- **为什么改 CPU**bge 调用频率低(织忆 recallCPU 推理够用,腾显存给 llama 让 7B 全 GPU
- **历史变化**
- v1bge 跑 GPUCUDAllama 跑 CPU → llama 太慢9 t/s
- v222:00 起bge 改 CPUllama 改 Vulkan GPU → llama 14 t/s提升 55%
- **切换方式**:编辑 `/home/muc/.hermes/scripts/bge_embed_server.py``providers=["CUDAExecutionProvider", "CPUExecutionProvider"]` 改成 `providers=["CPUExecutionProvider"]`
- **看门狗**`gpu-health-watchdog.sh` 不再把 bge-CPU 当异常
- bge venv = `/home/muc/.hermes/venvs/bge-embed/`(独立,**不污染** hermes 本体)
- 详见 skill: `bge-embed-crash-loop-fix`
### llama-server 正常态Vulkan GPU 推理2026-09-01 修复)
- **二进制路径**`/home/muc/.local/bin/llama-server`(稳定软链,不在 /tmp
- **systemd unit**`llama-server-7b.service`enabled开机自启
- **推理速度**Vulkan GPU ~14-15 t/s受限于 4GB 显存 + 7B Q3 3.6GB;纯 CPU ~9 t/s
- **编译命令**`cmake -B build -DGGML_VULKAN=ON -DGGML_CUDA=OFF` + `apt install libvulkan-dev glslc spirv-headers spirv-tools spirv-headers`
- **关键**`/tmp/` 会被 systemd-tmpfiles-clean 清掉systemd unit 永远写 `/home``/usr/local`
- 详见 skill: `self-healing-infrastructure``references/llama-vulkan-build-guide-20260901.md`
### 重建路径(`/tmp/memoryweave` 丢失时)

42
SOUL.md
View File

@ -157,6 +157,48 @@ cronjob 定时任务
---
## 🎯 任务执行铁律2026-09-01 固化)
**收到牧尘任务的本能反应**——以下规则每次必读:
### 1. 自动路由(不再问"用什么模式"
| 任务关键词 | 立即路由到 |
|-----------|-----------|
| 调研/分析/竞品/报告/选型/方案/设计 | **research** (agnes-2.0-flash) |
| 代码/实现/写/改/bug/插件/部署/飞书 | **default** (MiniMax-M3) |
| npc/NPC/云端编码/CodeBuddy/cnb/替我上班/公开项目 | **npc** (云端) |
| dsh/模型测试/本地模型/llama | **dsh**(额度用尽时 fallback default|
| 并行/并发/swarm/分身/多任务 | **Swarm 模式** |
| 默认 | research |
**禁止**:默认自己干 / 默认用 default profile / 问牧尘"用什么模式"。
### 2. 必走看板(除非秒答)
```bash
python3 ~/.hermes/scripts/kanban-route.py "任务" # 自动路由
python3 ~/.hermes/scripts/kanban-route.py "任务" --npc # 强制 NPC
python3 ~/.hermes/scripts/kanban-route.py "目标" --swarm --worker ... # 并行
```
### 3. 验证三步
- 任务创建后 → `hermes kanban list` 看 ready/running/done
- daemon 在跑 → `systemctl --user status kanban-daemon.service`
- 完成后看附件 → `~/.hermes/kanban/attachments/<task_id>/`
### 4. 失败 fallback 链
opencode/dsh 不可用 → 自动 fallback default → 紧急情况本体直接答
### 5. 路由规则会被 `kanban-router` skill 自动加载
- 看到"调研/分析/代码/实现/NPC/云端/本地/DSH/飞书"任何关键词 → 立刻调 `kanban-router` skill → 按规则路由
- 详细规则见 `~/.hermes/skills/kanban-router/routing-rules.md`
---
#### 触发方式 C任何"它是不是坏了"问题
| 看到关键词 | 立刻拉状态 |

View File

@ -140,41 +140,41 @@ checkpoints:
code_execution:
mode: project
command_allowlist:
- script execution via heredoc
- shell command via -c/-lc flag
- delete in root path
- overwrite system file via tee
- stop/restart system service
- pipe remote content to shell
- sudo with combined-flag privilege escalation
- kill process via pgrep expansion (self-termination)
- execute_code
- overwrite system file via redirection
- copy/move file into /etc/
- kill hermes/gateway process (self-termination)
- overwrite system config
- overwrite project env/config via redirection
- in-place edit of system config
- stop/restart hermes gateway (kills running agents)
- shell execution via heredoc
- copy/move file into sensitive credential/SSH/shell-rc path
- force kill processes (killall -KILL)
- find -delete
- in-place edit of Hermes config/env
- overwrite project env/config file
- script execution via -e/-c flag
- start gateway outside systemd (use 'systemctl --user restart hermes-gateway')
- git force push (rewrites remote history)
- disk copy
- hermes update (restarts gateway, kills running agents)
- recursive delete
- command parser limit or malformed executable payload
- SQL TRUNCATE
- copy/move file into system config path
- sudo with privilege flag (stdin/askpass/shell/list)
- world/other-writable permissions
- force kill processes
- git force push short flag (rewrites remote history)
- script execution via heredoc
- shell command via -c/-lc flag
- delete in root path
- overwrite system file via tee
- stop/restart system service
- pipe remote content to shell
- sudo with combined-flag privilege escalation
- kill process via pgrep expansion (self-termination)
- execute_code
- overwrite system file via redirection
- copy/move file into /etc/
- kill hermes/gateway process (self-termination)
- overwrite system config
- overwrite project env/config via redirection
- in-place edit of system config
- stop/restart hermes gateway (kills running agents)
- shell execution via heredoc
- copy/move file into sensitive credential/SSH/shell-rc path
- force kill processes (killall -KILL)
- find -delete
- in-place edit of Hermes config/env
- overwrite project env/config file
- script execution via -e/-c flag
- start gateway outside systemd (use 'systemctl --user restart hermes-gateway')
- git force push (rewrites remote history)
- disk copy
- hermes update (restarts gateway, kills running agents)
- recursive delete
- command parser limit or malformed executable payload
- SQL TRUNCATE
- copy/move file into system config path
- sudo with privilege flag (stdin/askpass/shell/list)
- world/other-writable permissions
- force kill processes
- git force push short flag (rewrites remote history)
compression:
abort_on_summary_failure: false
enabled: true
@ -192,7 +192,7 @@ cron:
gateway_required: true
model: glm-4-flash
model_provider: zhipu
provider: auto
provider: ''
wrap_response: true
curator:
archive_after_days: 90
@ -259,9 +259,9 @@ display:
resume_skip_tool_only: true
runtime_footer:
fields:
- model
- context_pct
- cwd
- model
- context_pct
- cwd
show_cost: false
show_reasoning: false
skin: default
@ -277,14 +277,14 @@ display:
first_lines: 2
last_lines: 2
fallback_providers:
- provider: agnes
model: agnes-2.0-flash
- provider: zhipu
model: glm-4-flash
- provider: sensenova
model: deepseek-v4-flash
- provider: opencode-free
model: x-preview-f-free
- provider: agnes
model: agnes-2.0-flash
- provider: zhipu
model: glm-4-flash
- provider: sensenova
model: deepseek-v4-flash
- provider: opencode-free
model: x-preview-f-free
file_read_max_chars: 100000
gateway:
media_delivery_allow_dirs: []
@ -312,7 +312,7 @@ kanban:
worker_log_rotate_bytes: 2097152
known_plugin_toolsets:
cli:
- spotify
- spotify
logging:
backup_count: 3
level: INFO
@ -339,16 +339,16 @@ mcp_servers:
enabled: true
codegraph:
args:
- serve
- --mcp
- serve
- --mcp
command: codegraph
connect_timeout: 60
enabled: true
timeout: 120
dbx:
args:
- -y
- '@dbx-app/mcp-server'
- -y
- '@dbx-app/mcp-server'
command: npx
connect_timeout: 60
enabled: true
@ -359,8 +359,8 @@ mcp_servers:
url: https://mcp.firecrawl.dev/v2/mcp
github:
args:
- -y
- '@modelcontextprotocol/server-github'
- -y
- '@modelcontextprotocol/server-github'
command: npx
enabled: true
env:
@ -368,8 +368,8 @@ mcp_servers:
timeout: 60
openclaw:
args:
- mcp
- serve
- mcp
- serve
command: openclaw
connect_timeout: 60
timeout: 300
@ -390,12 +390,12 @@ moa:
fanout: user_turn
reference_max_tokens: 800
reference_models:
- model: openai/gpt-oss-120b
provider: newapi-local
- model: nvidia/llama-3.3-nemotron-super-49b-v1
provider: newapi-local
- model: mistralai/mistral-large-3-675b-instruct-2512
provider: newapi-local
- model: openai/gpt-oss-120b
provider: newapi-local
- model: nvidia/llama-3.3-nemotron-super-49b-v1
provider: newapi-local
- model: mistralai/mistral-large-3-675b-instruct-2512
provider: newapi-local
save_traces: false
model:
api_key_env: ''
@ -422,23 +422,23 @@ paste_collapse_threshold: 5
paste_collapse_threshold_fallback: 5
platform_toolsets:
cli:
- browser
- clarify
- code_execution
- computer_use
- cronjob
- delegation
- file
- image_gen
- memory
- session_search
- skills
- terminal
- todo
- tts
- vision
- web
- mcp-codegraph
- browser
- clarify
- code_execution
- computer_use
- cronjob
- delegation
- file
- image_gen
- memory
- session_search
- skills
- terminal
- todo
- tts
- vision
- web
- mcp-codegraph
firecrawl:
connect_timeout: 30
timeout: 120
@ -456,9 +456,9 @@ platforms:
plugins:
disabled: []
enabled:
- agentic-sms-gateway
- cronalytics
- rtk-rewrite
- agentic-sms-gateway
- cronalytics
- rtk-rewrite
prefill_messages_file: ''
privacy:
redact_pii: false
@ -473,8 +473,8 @@ providers:
default_model: agnes-2.0-flash
key_env: AGNES_API_KEY
models:
- agnes-2.0-flash
- agnes-2.5-flash
- agnes-2.0-flash
- agnes-2.5-flash
rate_limit: 1000
timeout: 60
deepseek:
@ -483,8 +483,8 @@ providers:
default_model: deepseek-v4-flash
key_env: DEEPSEEK_API_KEY
models:
- deepseek-v4-flash
- deepseek-v4-pro
- deepseek-v4-flash
- deepseek-v4-pro
rate_limit: 1000
timeout: 30
mimo:
@ -493,8 +493,8 @@ providers:
default_model: mimo-v2.5-pro
key_env: XIAOMI_API_KEY
models:
- mimo-v2.5-pro
- mimo-v2.5
- mimo-v2.5-pro
- mimo-v2.5
rate_limit: 1000
timeout: 60
newapi-local:
@ -503,10 +503,10 @@ providers:
cost_factor: 0.0
default_model: agnes-2.0-flash
models:
- nvidia/nemotron-mini-4b-instruct
- openai/gpt-oss-120b
- nvidia/nvidia-nemotron-nano-9b-v2
- meta/llama-3.1-8b-instruct
- nvidia/nemotron-mini-4b-instruct
- openai/gpt-oss-120b
- nvidia/nvidia-nemotron-nano-9b-v2
- meta/llama-3.1-8b-instruct
rate_limit: 1000
timeout: 30
omniroute-local:
@ -515,9 +515,9 @@ providers:
cost_factor: 0.0
default_model: auto/chat
models:
- auto/chat
- auto/best-free
- auto/coding
- auto/chat
- auto/best-free
- auto/coding
rate_limit: 1000
timeout: 30
opencode-free:
@ -526,12 +526,12 @@ providers:
cost_factor: 0.0
default_model: x-preview-f-free
models:
- x-preview-f-free
- hy3-free
- laguna-s-2.1-free
- nemotron-3-ultra-free
- nemotron-3.5-lightning-free
- muse-spark-1.2-contributor-free
- x-preview-f-free
- hy3-free
- laguna-s-2.1-free
- nemotron-3-ultra-free
- nemotron-3.5-lightning-free
- muse-spark-1.2-contributor-free
rate_limit: 1000
timeout: 30
sensenova:
@ -540,18 +540,18 @@ providers:
cost_factor: 0.0
default_model: deepseek-v4-flash
models:
- deepseek-v4-flash
- glm-5.2
- sensenova-6.8-flash-lite
- deepseek-v4-flash
- glm-5.2
- sensenova-6.8-flash-lite
rate_limit: 1000
timeout: 30
zhipu:
api_key: ${ZHIPU_API_KEY}
base_url: https://open.bigmodel.cn/api/paas/v4
models:
- glm-4-flash
- glm-4-air
- glm-4-long
- glm-4-flash
- glm-4-air
- glm-4-long
type: openai
llama-local:
api_key: local-key
@ -559,8 +559,8 @@ providers:
cost_factor: 0.0
default_model: qwen3b
models:
- qwen3b
- qwen7b
- qwen3b
- qwen7b
rate_limit: 100
timeout: 300
context_length: 131072
@ -673,8 +673,8 @@ tools:
search_default_limit: 5
threshold_pct: 10
toolsets:
- hermes-cli
- web
- hermes-cli
- web
tts:
edge:
voice: zh-CN-XiaoyiNeural

View File

@ -37,7 +37,7 @@ sess_options.enable_mem_pattern = False # 禁用内存模式,避免碎片
session = ort.InferenceSession(
os.path.join(MODEL_PATH, MODEL_FILE),
sess_options=sess_options,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
providers=["CPUExecutionProvider"],
)
log.info("ONNX 模型就绪 — providers=%s", session.get_providers())

View File

@ -0,0 +1,147 @@
"""织忆 MemoryWeave — bge-m3 ONNX 嵌入服务器
OpenAI /v1/embeddings 兼容接口Go 代码零改动切换。
使用 ONNX Runtime CPU 推理RTX 3050 4GB 无压力。
启动: python3 bge_embed_server.py
端口: 8000
模型: /home/muc/models/bge-m3/onnx/
"""
import json
import logging
import math
import os
from http.server import HTTPServer, BaseHTTPRequestHandler
import numpy as np
import onnxruntime as ort
from transformers import AutoTokenizer
MODEL_PATH = os.environ.get("BGE_MODEL_PATH", "/home/muc/models/bge-m3/onnx")
MODEL_FILE = os.environ.get("BGE_MODEL_FILE", "model.onnx")
PORT = int(os.environ.get("BGE_PORT", "8000"))
MAX_BATCH = int(os.environ.get("BGE_MAX_BATCH", "32"))
logging.basicConfig(level=logging.INFO, format="[bge-embed] %(message)s")
log = logging.getLogger(__name__)
# ─── 初始化 ──────────────────────────────────────────
log.info("加载 tokenizer: %s", MODEL_PATH)
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
log.info("加载 ONNX 模型: %s/%s", MODEL_PATH, MODEL_FILE)
sess_options = ort.SessionOptions()
sess_options.intra_op_num_threads = 4
sess_options.inter_op_num_threads = 2
sess_options.enable_cpu_mem_arena = False # 禁用 arena 分配器,防止内存逐渐扩大
sess_options.enable_mem_pattern = False # 禁用内存模式,避免碎片累积
session = ort.InferenceSession(
os.path.join(MODEL_PATH, MODEL_FILE),
sess_options=sess_options,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
)
log.info("ONNX 模型就绪 — providers=%s", session.get_providers())
def encode(texts: list[str]) -> list[list[float]]:
"""批量编码 + mean pooling + L2 归一化"""
inputs = tokenizer(
texts,
padding=True,
truncation=True,
max_length=8192,
return_tensors="np",
)
ort_inputs = {
"input_ids": inputs["input_ids"],
"attention_mask": inputs["attention_mask"],
}
outputs = session.run(None, ort_inputs)
# ONNX 输出: [batch, seq_len, 1024] — token-level embeddings
embeddings: np.ndarray = outputs[0]
# Mean pooling — 按 attention_mask 加权平均
attention_mask = inputs["attention_mask"].astype(np.float32)
mask_expanded = np.expand_dims(attention_mask, -1) # [batch, seq_len, 1]
sum_embeddings = np.sum(embeddings * mask_expanded, axis=1) # [batch, 1024]
sum_mask = np.clip(np.sum(mask_expanded, axis=1), 1e-9, None) # [batch, 1]
embeddings = sum_embeddings / sum_mask # [batch, 1024]
# L2 归一化
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
norms = np.maximum(norms, 1e-12)
embeddings = embeddings / norms
return embeddings.tolist()
class EmbedHandler(BaseHTTPRequestHandler):
"""OpenAI /v1/embeddings 兼容"""
def log_message(self, fmt, *args):
pass # 安静模式
def _respond(self, code: int, data: dict):
body = json.dumps(data, ensure_ascii=False).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def do_GET(self):
if self.path == "/health":
self._respond(200, {"status": "ok", "model": "bge-m3", "backend": "onnxruntime", "providers": session.get_providers()})
else:
self._respond(404, {"error": "not found"})
def do_POST(self):
if self.path != "/v1/embeddings":
self._respond(404, {"error": "not found"})
return
content_len = int(self.headers.get("Content-Length", 0))
body = json.loads(self.rfile.read(content_len))
inputs = body.get("input", [])
if isinstance(inputs, str):
inputs = [inputs]
if not inputs:
self._respond(400, {"error": "empty input"})
return
if len(inputs) > MAX_BATCH:
self._respond(400, {"error": f"batch size {len(inputs)} > max {MAX_BATCH}"})
return
try:
embeddings = encode(inputs)
except Exception as e:
log.error("encode error: %s", e)
self._respond(500, {"error": str(e)})
return
data = [
{"embedding": emb, "index": i, "object": "embedding"}
for i, emb in enumerate(embeddings)
]
self._respond(200, {
"object": "list",
"data": data,
"model": "bge-m3",
"usage": {"prompt_tokens": sum(len(t) for t in inputs), "total_tokens": sum(len(t) for t in inputs)},
})
def main():
server = HTTPServer(("0.0.0.0", PORT), EmbedHandler)
log.info("bge-m3 ONNX 嵌入服务器启动 — http://0.0.0.0:%d", PORT)
log.info("端点: POST /v1/embeddings GET /health")
try:
server.serve_forever()
except KeyboardInterrupt:
log.info("关闭服务器")
server.shutdown()
if __name__ == "__main__":
main()

View File

@ -2,20 +2,39 @@
# GPU 健康看门狗 — 监控 bge-embed 服务 + 显存 + 磁盘
# 正常静默,异常报警
#
# 设计说明2026-09-01 更新):
# bge-m3 用 onnxruntime-gpu + CUDAExecutionProvider 推理;显存耗用 ~600MB。
# 如果 /health 返回的是 CPUExecutionProvider 而非 CUDAExecutionProvider
# 说明 onnxruntime CUDA provider 加载失败常见原因nvidia runtime 库路径
# 找不到 / 缺 cuDNN。此时必须报警。
# 设计说明2026-09-01 更新 v2
#
# GPU 4GB 笔记本 + bge-embed + llama 7B 共存。优先级:
# 1. llama-server 必须在 GPU推理用→ /health 必须含 CUDA
# 2. bge-embed 可在 CPU 或 GPUembedding 低频)→ 不强求 CUDA
# 3. GPU 显存 > 92% 报警
#
# 之前"bge 必须 CUDA"的判断是错的——bge 改 CPU 是为腾显存给 llama。
# 现在 bge 状态不报警(除非 /health 完全无响应)。
set -e
ALERT=""
# 1. bge-embed 服务(检查端口响应 + 必须用 CUDAExecutionProvider
# 1. llama-server-7b必须 GPU + 端口响应)
LLAMA_HEALTH=$(curl -s -m 5 http://localhost:8080/health 2>/dev/null || true)
if [ -z "$LLAMA_HEALTH" ]; then
# /health 无响应 → 端口未监听 → 真异常
ALERT="$ALERT\n❌ llama-server-7b 服务异常(/health 无响应,:8080 未监听)"
systemctl --user restart llama-server-7b 2>/dev/null || true
sleep 8
LLAMA_HEALTH2=$(curl -s -m 5 http://localhost:8080/health 2>/dev/null || true)
if [ -z "$LLAMA_HEALTH2" ]; then
ALERT="$ALERT\n❌ llama-server-7b 重启后仍未恢复"
else
ALERT="$ALERT\n✅ llama-server-7b 已自动重启恢复"
fi
fi
# 2. bge-embed必须响应CPU/GPU 都 OK
BGE_HEALTH=$(curl -s -m 5 http://localhost:8000/health 2>/dev/null || true)
if [ -z "$BGE_HEALTH" ]; then
# /health 无响应 → 端口未监听 → 真异常 → 重试
ALERT="$ALERT\n❌ bge-embed 服务异常(/health 无响应)"
# /health 无响应 → 端口未监听 → 真异常
ALERT="$ALERT\n❌ bge-embed 服务异常(/health 无响应:8000 未监听"
systemctl --user restart bge-embed 2>/dev/null || true
sleep 5
BGE_HEALTH2=$(curl -s -m 5 http://localhost:8000/health 2>/dev/null || true)
@ -24,9 +43,6 @@ if [ -z "$BGE_HEALTH" ]; then
else
ALERT="$ALERT\n✅ bge-embed 已自动重启恢复"
fi
elif ! echo "$BGE_HEALTH" | grep -q "CUDAExecutionProvider"; then
# 端口在,但没用 CUDA — 异常CUDA runtime 库路径/版本可能有问题
ALERT="$ALERT\n❌ bge-embed 起来了但未用 CUDAExecutionProviderproviders=$BGE_HEALTH"
fi
# 2. GPU 状态

168
scripts/npc-dispatch.py Executable file
View File

@ -0,0 +1,168 @@
#!/usr/bin/env python3
"""
CNB CodeBuddy NPC 看板调度器
通过 Issue @npc/CodeBuddy 触发云端编码
用法:
# 创建仓库
python3 npc-dispatch.py --create-repo muchen-org/test-repo
# 触发任务(自动建 Issue + @CodeBuddy 启动)
python3 npc-dispatch.py "实现一个 HTTP 服务器" --repo muchen-org/test-repo
# 带验收标准
python3 npc-dispatch.py "写 REST API" --repo muchen-org/test --criteria "有单元测试" "支持 POST/GET"
"""
import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path
API = "https://api.cnb.cool"
def load_token():
"""从 ~/.hermes/.env 读取 CNB_TOKEN"""
env_path = Path.home() / ".hermes" / ".env"
try:
with open(env_path) as f:
for line in f:
line = line.strip()
if line.startswith("CNB_TOKEN="):
return line.split("=", 1)[1].strip()
except FileNotFoundError:
pass
return os.environ.get("CNB_TOKEN", "")
def api_call(method, path, data=None):
"""CNB API 统一调用"""
token = load_token()
if not token:
print("❌ CNB_TOKEN 未配置", file=sys.stderr)
sys.exit(1)
url = f"{API}{path}"
headers = {
"Authorization": f"Bearer {token}",
"accept": "application/json",
"Content-Type": "application/json",
}
body = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
content = resp.read().decode()
# 空响应201 Created直接返回 None
if not content.strip():
return {"status": resp.status, "empty": True}
return json.loads(content)
except urllib.error.HTTPError as e:
error_body = e.read().decode()
print(f"❌ HTTP {e.code}: {error_body}", file=sys.stderr)
sys.exit(1)
def create_repo(org, name, description="", visibility="public"):
"""创建仓库"""
result = api_call("POST", f"/{org}/-/repos", {
"name": name,
"description": description,
"visibility": visibility,
})
# 201 Created 返回空 body用 path/name 构造结果
if result.get("empty"):
return {"path": f"{org}/{name}", "name": name, "visibility": visibility}
return result
def create_issue_with_npc(repo, task_title, task_body, criteria=None, work_mode=True):
"""创建 Issue 并触发 NPC"""
# 构造 body
body_parts = [f"## 任务描述\n{task_body}\n"]
if criteria:
body_parts.append("## 验收标准\n")
for i, c in enumerate(criteria, 1):
body_parts.append(f"{i}. {c}")
body = "\n".join(body_parts)
# 创建 Issue带 work_mode: true
issue = api_call("POST", f"/{repo}/-/issues", {
"title": f"@npc/CodeBuddy 替我上班:{task_title}",
"body": body,
"labels": ["enhancement"],
"priority": "P2",
"work_mode": work_mode,
})
print(f"✅ Issue 已创建: #{issue.get('number')} - {issue.get('title')}")
# 立即发评论再次触发(保险起见)
issue_number = issue.get("iid") or issue.get("number")
comment = api_call("POST", f"/{repo}/-/issues/{issue_number}/comments", {
"body": "@npc/CodeBuddy 替我上班,请开始执行这个任务。",
"work_mode": True,
})
print(f"✅ 触发评论已发: NPC 约 3 分钟内开始响应")
return {
"issue_number": issue_number,
"title": issue.get("title"),
"url": issue.get("html_url"),
"state": issue.get("state"),
}
def dispatch_to_npc(task_desc, repo="", criteria=None):
"""主入口:分派任务给 NPC"""
if not repo:
repo = os.environ.get("CNB_DEFAULT_REPO", "muchen-org/auto-npc")
# 确保仓库存在
repo_info = None
try:
repo_info = api_call("GET", f"/{repo}")
except SystemExit:
print(f"📦 仓库 {repo} 不存在,尝试创建...")
if not repo or "/" not in repo:
print("❌ 需要有效的 org/repo 格式", file=sys.stderr)
sys.exit(1)
org, name = repo.split("/", 1)
create_repo(org, name, description="Auto-created by 小唯看板")
repo_info = api_call("GET", f"/{repo}")
# 提取简短标题
short_title = task_desc[:60] + ("..." if len(task_desc) > 60 else "")
return create_issue_with_npc(repo, short_title, task_desc, criteria)
def main():
parser = argparse.ArgumentParser(description="CNB CodeBuddy NPC 看板调度器")
parser.add_argument("task", nargs="?", help="任务描述")
parser.add_argument("--repo", help="目标仓库 (org/name)")
parser.add_argument("--criteria", nargs="+", help="验收标准(多项)")
parser.add_argument("--create-repo", metavar="ORG/NAME", help="创建仓库")
parser.add_argument("--no-work-mode", action="store_true", help="禁用 work_mode仅编码不能 push")
args = parser.parse_args()
if args.create_repo:
org, name = args.create_repo.split("/", 1)
result = create_repo(org, name)
print(f"✅ 仓库已创建: {result.get('path') or args.create_repo}")
return
if not args.task:
parser.print_help()
sys.exit(1)
result = dispatch_to_npc(args.task, args.repo, args.criteria)
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,144 @@
# Kanban 路由能力固化报告
> 2026-09-01 · v1.0 · 小唯 A06
> 目标:"不用提醒,我就知道用什么模式运行"
---
## ✅ 三层固化方案已落地
| 层 | 文件 | 作用 | 触发时机 |
|---|------|------|----------|
| **L1 技能层** | `~/.hermes/skills/kanban-router/SKILL.md` | 关键词→profile 自动扫描加载 | 每次任务有"调研/分析/代码/NPC/DSH/飞书"等关键词时自动加载 |
| **L2 规则层** | `~/.hermes/skills/kanban-router/routing-rules.md` | 完整规则文档(关键词/Profile/fallback| skill 内部引用 |
| **L3 铁律层** | `~/.hermes/SOUL.md` 新增章节"任务执行铁律" | 每会话注入到我的核心人格 | 每次会话开始 |
---
## 📋 L1 技能层(`kanban-router` SKILL.md
### 自动加载关键词
```
description: "Use when 牧尘 gives a task that needs delegation.
Routes to NPC/DSH/opencode/research profile based on keywords.
Triggered by: 调研/分析/代码/实现/NPC/云端/本地/DSH/飞书/deploy."
```
**触发词覆盖**:调研/分析/代码/实现/NPC/云端/本地/DSH/飞书/deploy 等 30+ 关键词
### 路由表(固化在 skill 里)
| 触发词 | Profile | 模型 |
|-------|---------|------|
| 调研/分析/竞品/报告/选型/方案/设计 | research | agnes-2.0-flash |
| 代码/实现/写/改/bug/插件/部署/飞书 | default | MiniMax-M3 |
| npc/NPC/云端编码/CodeBuddy/cnb/替我上班 | npc | deepseek-v4-flash |
| dsh/模型测试/本地模型/llama | dsh | deepseek-v4-flash |
| 并行/并发/swarm/分身 | Swarm 模式 | 多 worker |
---
## 📋 L2 规则层(`routing-rules.md`
### 5 章节内容
1. **完整关键词表**:精确匹配 + 模糊匹配(按 5 类 profile 分类)
2. **Profile 详解**:每个 profile 的能力/限制/使用场景
3. **失败 Fallback 链**opencode/dsh → default → 本体
4. **Swarm 拓扑模板**4-worker / 2-worker 标准模板
5. **NPC 任务特殊处理**:仓库创建 / 触发评论 / 查看 PR
6. **命令速查表**:所有命令一行查询
---
## 📋 L3 铁律层SOUL.md 新章节)
位置:`~/.hermes/SOUL.md` 第 158-201 行(新加的"任务执行铁律"
### 5 条铁律
1. **自动路由**(不再问"用什么模式"
2. **必走看板**(除非秒答)
3. **验证三步**(看板 list / daemon 状态 / attachments
4. **失败 fallback 链**
5. **路由规则会被 `kanban-router` skill 自动加载**
### 关键设计
- 用了"禁止:默认自己干 / 默认用 default profile / 问牧尘'用什么模式'"这种**反例铁律**
- 让 SOUL.md 的"反向约束"机制管住我(沉默 = 失职的反面也适用:路由失败 = 失职)
---
## 🧪 实测验证
### 测试 1research 路由
```bash
$ python3 ~/.hermes/scripts/kanban-route.py "测试路由:调研 Python 异步框架对比"
✅ 任务已创建: t_ba32ea18 (research)
```
### 测试 2NPC 路由
```bash
$ python3 ~/.hermes/scripts/kanban-route.py "测试 NPC 路由固化:让 NPC 写个函数" --npc
✅ Issue 已创建: #1 in muchen-org/auto-npc
```
### 测试 3default 路由
```bash
$ python3 ~/.hermes/scripts/kanban-route.py "写一个 Python 函数:求两个数最大值"
✅ 任务已创建: t_f4900138 (default, 30s 完成)
```
**三路径全部跑通** ✅
---
## 🎯 固化效果
### Before之前
- ❌ 牧尘说"调研 XX" → 我问"用什么模式?"
- ❌ 牧尘说"写代码" → 我直接用 default profile 干
- ❌ 每次都要牧尘提醒"NPC / Swarm / 看板"
### After现在
- ✅ 牧尘说"调研 XX" → 我自动调 kanban-router skill → 路由到 research → 创建看板
- ✅ 牧尘说"写代码" → 我自动路由到 default + 创建看板 + daemon 执行
- ✅ 牧尘说"NPC 写 XX" → 我自动路由到 npc + 创建 Issue + @CodeBuddy
- ✅ 牧尘说"并行做 A 和 B" → 我自动 Swarm + 多 worker
---
## 📁 固化产出3 个新文件 + 1 个修改)
| 文件 | 大小 | 用途 |
|------|------|------|
| `~/.hermes/skills/kanban-router/SKILL.md` | 4343 字节 | L1 自动加载 |
| `~/.hermes/skills/kanban-router/routing-rules.md` | 4433 字节 | L2 详细规则 |
| `~/.hermes/scripts/routing-固化报告.md` | 本文档 | 验证记录 |
| `~/.hermes/SOUL.md` | +42 行 | L3 铁律 |
| `~/.hermes/scripts/npc-dispatch.py` | 修复 | 默认 repo fallback |
---
## 🔄 进化路径
### 现在v1.0
- 5 类智能体路由固化
- 3 路径全部跑通
- daemon 持久化
### 下次优化
- 失败自动重试 + 智能 fallback不靠 fallback 链硬编码)
- Swarm 完成度自动检测(不靠 verifier todo 状态)
- NPC 完成时飞书通知
---
*完成人:小唯 A06*
*固化日期2026-09-01 21:35*
*状态:✅ 全部固化完成*

View File

@ -0,0 +1,71 @@
import os, sys
os.environ['HERMES_HOME'] = '/home/muc/.hermes'
sys.path.insert(0, '/home/muc/.hermes/hermes-agent')
from plugins.memory.holographic.store import MemoryStore
from plugins.memory.holographic.retrieval import FactRetriever
DB = '/home/muc/.hermes/memory_store.db'
if os.path.exists(DB): os.remove(DB)
print("=" * 60)
print("HOLO Test - 9 actions + feedback")
print("=" * 60)
store = MemoryStore(DB)
retr = FactRetriever(store)
print("\n[1] ADD_FACT 5 facts")
f1 = store.add_fact("小唯喜欢 16yo 172cm porcelain skin 人物设定", category="preference")
f2 = store.add_fact("商汤 SenseNova 出图大于 Agnes", category="tool")
f3 = store.add_fact("删 skill 前必须经牧尘同意", category="rule")
f4 = store.add_fact("v0.21.0 已升级 gateway 跑通", category="event")
f5 = store.add_fact("KOCR 修复铁律:只动需要改的列", category="rule")
print(f"IDs: {[f1, f2, f3, f4, f5]}")
print("\n[2] LIST_FACTS")
for f in store.list_facts():
print(f" [{f['fact_id']}] trust={f['trust_score']:.2f} {f['category']}: {f['content'][:50]}")
print("\n[3] SEARCH 'KOCR'")
for r in retr.search("KOCR", limit=3):
print(f" [{r['fact_id']}] score={r.get('score',0):.3f}: {r['content'][:50]}")
print("\n[4] PROBE '人物设定'")
for r in retr.probe("人物设定", limit=3):
print(f" [{r['fact_id']}] score={r.get('score',0):.3f}: {r['content'][:50]}")
print("\n[5] RELATED 'KOCR'")
for r in retr.related("KOCR", limit=3):
print(f" [{r['fact_id']}]: {r['content'][:50]}")
print("\n[6] REASON ['人物设定', 'preference']")
for r in retr.reason(["人物设定", "preference"], limit=3):
print(f" [{r['fact_id']}] conf={r.get('confidence',0):.3f}: {r['content'][:50]}")
print("\n[7] CONTRADICT (scan all category=tool)")
f6 = store.add_fact("Agnes 出图比商汤好", category="tool")
contras = retr.contradict(category="tool", limit=5)
print(f" f6: {f6}, 矛盾扫描: {len(contras)}")
for c in contras[:5]:
print(f" [{c['fact_id']}]: {c['content'][:50]}")
print("\n[8] UPDATE_FACT f4 trust_delta -0.25")
print(f" Result: {store.update_fact(f4, trust_delta=-0.25)}")
print("\n[9] REMOVE_FACT f6")
print(f" Result: {store.remove_fact(f6)}")
print("\n[10] FEEDBACK f1 helpful=True")
print(f" Result: {store.record_feedback(f1, helpful=True)}")
print("\n[11] FEEDBACK f2 helpful=False")
print(f" Result: {store.record_feedback(f2, helpful=False)}")
print("\n" + "=" * 60)
print("FINAL (含 trust 变化)")
print("=" * 60)
for f in store.list_facts():
print(f" [{f['fact_id']}] trust={f['trust_score']:.2f} help={f['helpful_count']}: {f['content'][:50]}")
print(f"\nTotal: {len(store.list_facts())} facts")
print(f"DB size: {os.path.getsize(DB)} bytes")
store.close()

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,9 @@
{
"last_report_path": "/home/muc/.hermes/profiles/prof-b/logs/curator/20260825-143653",
"last_run_at": "2026-08-25T14:36:53.254154+00:00",
"last_run_duration_seconds": 1.43867,
"last_run_summary": "auto: 1 marked stale, 1 reactivated; llm: skipped (consolidation off)",
"last_report_path": "/home/muc/.hermes/logs/curator/20260901-145714",
"last_run_at": "2026-09-01T14:57:14.689382+00:00",
"last_run_duration_seconds": 5.117237,
"last_run_summary": "auto: 2 marked stale, 1 reactivated; llm: skipped (consolidation off)",
"last_run_summary_shown_at": null,
"paused": false,
"run_count": 16
"run_count": 17
}

View File

@ -344,16 +344,16 @@
"archived_at": null,
"created_at": "2026-07-23T09:27:16.552310+00:00",
"created_by": "agent",
"last_patched_at": "2026-07-23T14:07:01.001333+00:00",
"last_reused_patch_generation": 0,
"last_used_at": "2026-09-01T12:24:50.584267+00:00",
"last_viewed_at": "2026-09-01T12:24:50.579574+00:00",
"patch_count": 2,
"patch_generation": 0,
"last_patched_at": "2026-09-01T13:24:30.353399+00:00",
"last_reused_patch_generation": 2,
"last_used_at": "2026-09-01T14:46:17.329481+00:00",
"last_viewed_at": "2026-09-01T14:46:17.312828+00:00",
"patch_count": 4,
"patch_generation": 2,
"pinned": false,
"state": "active",
"use_count": 6,
"view_count": 6
"use_count": 16,
"view_count": 16
},
"blocked-page-recovery": {
"archived_at": null,
@ -540,16 +540,16 @@
"archived_at": null,
"created_at": "2026-08-09T12:25:17.949527+00:00",
"created_by": "agent",
"last_patched_at": "2026-08-15T17:13:55.335192+00:00",
"last_reused_patch_generation": 12,
"last_used_at": "2026-08-15T17:13:11.742416+00:00",
"last_viewed_at": "2026-08-15T17:13:11.738829+00:00",
"patch_count": 13,
"patch_generation": 13,
"last_patched_at": "2026-09-01T13:35:53.465773+00:00",
"last_reused_patch_generation": 14,
"last_used_at": "2026-09-01T13:32:55.589413+00:00",
"last_viewed_at": "2026-09-01T13:32:55.580024+00:00",
"patch_count": 16,
"patch_generation": 16,
"pinned": false,
"state": "active",
"use_count": 6,
"view_count": 6
"use_count": 11,
"view_count": 11
},
"code-intelligence": {
"archived_at": null,
@ -839,14 +839,29 @@
"created_by": "agent",
"last_patched_at": "2026-08-26T05:42:43.675272+00:00",
"last_reused_patch_generation": 2,
"last_used_at": "2026-09-01T12:24:33.964455+00:00",
"last_viewed_at": "2026-09-01T12:24:33.959928+00:00",
"last_used_at": "2026-09-01T12:49:40.492187+00:00",
"last_viewed_at": "2026-09-01T12:49:40.486311+00:00",
"patch_count": 98,
"patch_generation": 2,
"pinned": false,
"state": "active",
"use_count": 87,
"view_count": 87
"use_count": 88,
"view_count": 88
},
"devops/bge-embed-crash-loop-fix": {
"archived_at": null,
"created_at": "2026-09-01T13:33:34.311889+00:00",
"created_by": null,
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": "2026-09-01T14:46:28.274044+00:00",
"last_viewed_at": "2026-09-01T14:46:28.264935+00:00",
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 4,
"view_count": 4
},
"devops/devops-umbrella": {
"archived_at": null,
@ -887,6 +902,21 @@
"use_count": 2,
"view_count": 2
},
"devops/self-healing-infrastructure": {
"archived_at": null,
"created_at": "2026-09-01T14:46:35.641907+00:00",
"created_by": null,
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": "2026-09-01T14:46:35.671454+00:00",
"last_viewed_at": "2026-09-01T14:46:35.658080+00:00",
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 3,
"view_count": 3
},
"devops/self-hosted-tunneling": {
"archived_at": null,
"created_at": "2026-06-19T19:10:21.517068+00:00",
@ -1223,6 +1253,21 @@
"use_count": 18,
"view_count": 18
},
"github": {
"archived_at": null,
"created_at": "2026-09-01T14:57:19.593565+00:00",
"created_by": null,
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": null,
"last_viewed_at": null,
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 0,
"view_count": 0
},
"github-auth": {
"archived_at": null,
"created_at": "2026-06-30T11:24:39.499832+00:00",
@ -1415,7 +1460,7 @@
"last_viewed_at": "2026-08-01T14:20:59.356526+00:00",
"patch_count": 4,
"pinned": false,
"state": "active",
"state": "stale",
"use_count": 2,
"view_count": 2
},
@ -1526,14 +1571,14 @@
"created_by": null,
"last_patched_at": "2026-08-28T13:02:49.751591+00:00",
"last_reused_patch_generation": 2,
"last_used_at": "2026-09-01T12:14:15.767842+00:00",
"last_viewed_at": "2026-09-01T12:14:15.759086+00:00",
"last_used_at": "2026-09-01T13:32:55.584589+00:00",
"last_viewed_at": "2026-09-01T13:32:55.575007+00:00",
"patch_count": 125,
"patch_generation": 2,
"pinned": false,
"state": "active",
"use_count": 146,
"view_count": 145
"use_count": 148,
"view_count": 147
},
"hermes-desktop-kanban": {
"archived_at": null,
@ -1610,14 +1655,14 @@
"created_by": null,
"last_patched_at": "2026-08-12T05:13:32.138839+00:00",
"last_reused_patch_generation": 1,
"last_used_at": "2026-08-30T09:41:13.404893+00:00",
"last_viewed_at": "2026-08-30T09:41:13.395293+00:00",
"last_used_at": "2026-09-01T13:17:29.163415+00:00",
"last_viewed_at": "2026-09-01T13:17:29.139591+00:00",
"patch_count": 81,
"patch_generation": 1,
"pinned": false,
"state": "active",
"use_count": 138,
"view_count": 127
"use_count": 139,
"view_count": 128
},
"hermes-venv-dependency-safety": {
"archived_at": null,
@ -1759,14 +1804,44 @@
"created_by": null,
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": "2026-09-01T12:20:29.962075+00:00",
"last_viewed_at": "2026-09-01T12:20:29.928252+00:00",
"last_used_at": "2026-09-01T13:33:05.681821+00:00",
"last_viewed_at": "2026-09-01T13:33:05.672818+00:00",
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 1,
"view_count": 1
"use_count": 5,
"view_count": 5
},
"kanban-routing": {
"archived_at": null,
"created_at": "2026-09-01T13:21:22.328206+00:00",
"created_by": "agent",
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": null,
"last_viewed_at": null,
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 0,
"view_count": 0
},
"kanban-worker": {
"archived_at": null,
"created_at": "2026-09-01T13:25:03.696552+00:00",
"created_by": null,
"last_patched_at": null,
"last_reused_patch_generation": 0,
"last_used_at": "2026-09-01T13:33:05.677344+00:00",
"last_viewed_at": "2026-09-01T13:33:05.660452+00:00",
"patch_count": 0,
"patch_generation": 0,
"pinned": false,
"state": "active",
"use_count": 2,
"view_count": 2
},
"karpathy-code-discipline": {
"archived_at": null,
@ -1908,7 +1983,7 @@
"patch_count": 36,
"patch_generation": 3,
"pinned": false,
"state": "stale",
"state": "active",
"use_count": 44,
"view_count": 44
},
@ -2341,7 +2416,7 @@
"last_viewed_at": "2026-07-31T18:14:50.228349+00:00",
"patch_count": 4,
"pinned": false,
"state": "active",
"state": "stale",
"use_count": 9,
"view_count": 9
},
@ -2620,14 +2695,14 @@
"created_by": "agent",
"last_patched_at": "2026-08-29T12:20:33.818984+00:00",
"last_reused_patch_generation": 2,
"last_used_at": "2026-08-29T13:12:08.253798+00:00",
"last_viewed_at": "2026-08-29T13:12:08.244932+00:00",
"last_used_at": "2026-09-01T12:59:01.978539+00:00",
"last_viewed_at": "2026-09-01T12:59:01.974290+00:00",
"patch_count": 2,
"patch_generation": 2,
"pinned": false,
"state": "active",
"use_count": 8,
"view_count": 8
"use_count": 9,
"view_count": 9
},
"provider-tiering": {
"archived_at": null,
@ -2784,16 +2859,16 @@
"archived_at": null,
"created_at": "2026-07-08T18:13:02.034240+00:00",
"created_by": "agent",
"last_patched_at": "2026-08-25T09:13:48.358348+00:00",
"last_reused_patch_generation": 25,
"last_used_at": "2026-08-30T08:57:58.764192+00:00",
"last_viewed_at": "2026-08-30T08:57:58.754342+00:00",
"patch_count": 228,
"patch_generation": 25,
"last_patched_at": "2026-09-01T13:48:27.218650+00:00",
"last_reused_patch_generation": 27,
"last_used_at": "2026-09-01T14:46:17.334034+00:00",
"last_viewed_at": "2026-09-01T14:46:17.325238+00:00",
"patch_count": 230,
"patch_generation": 27,
"pinned": false,
"state": "active",
"use_count": 180,
"view_count": 180
"use_count": 191,
"view_count": 191
},
"self-hosted-tunneling": {
"archived_at": null,

View File

@ -1,7 +1,7 @@
---
name: bge-embed-crash-loop-fix
version: 2026-09-01
description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺依赖)的诊断与修复
version: 2026-09-01-v2
description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺依赖 / CPU-GPU 切换设计)的诊断与修复
---
# bge-embed Crash Loop 排查与修复
@ -9,6 +9,30 @@ description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺
## 症状
bge-embed.service 反复崩溃重启,重启计数飙到 85337 次,但看起来"在线"。
## ⚠️ 修复前必做:拉现状 4 步(不要先猜根因)
2026-09-01 教训:我没拉现状就下结论"CPU 是这台 4GB 笔记本正常态",错了。看门狗报"未用 CUDA"被当成"看门狗误报"处理,结果把正确的报警逻辑改坏。**任何 bge 修复前必须先拉真实状态**
```bash
# 1. 服务在不在?(不是 systemd statussystemd 可能撒谎)
pgrep -fa 'bge_embed_server.py' | head -3
# 2. /health 实际返回什么?(**真**在用什么 provider
curl -s http://localhost:8000/health
# 3. GPU 实际占用CPU fallback 200MB / CUDA 600MB 一眼分)
nvidia-smi --query-gpu=memory.used --format=csv,noheader
# 4. bge 进程加载的 .so验证 CUDA 库真的在用)
BGE_PID=$(pgrep -f bge_embed_server.py | head -1)
cat /proc/$BGE_PID/maps 2>/dev/null | grep -oE "lib(cudart|cublas|cudnn|onnxruntime)[^ ]*\.so[^ ]*" | sort -u
```
**根据上面 4 步选根因**
- `pgrep` 没有进程 + NRestarts 疯涨 → 根因 A/B端口冲突或僵尸进程
- 进程在但 /health 是 `ModuleNotFoundError`import 失败) → 根因 Cvenv 缺依赖)
- 进程在、/health 200、但 `providers: ["CPUExecutionProvider"]` → 根因 C2装了 CPU 版 onnxruntime 或 CUDA 库路径不对)
- 进程在、/health 200、`providers: ["CUDAExecutionProvider", ...]`、显存 600MB → **健康,不用修**(看门狗误报是另一码事)
**陷阱**:不要把"看门狗报未用 CUDA"当成"看门狗误报"——它可能就是真相。先拉现状再下判断。
## 诊断命令
```bash
# 日志
@ -154,5 +178,36 @@ curl -s http://localhost:8000/health # 确认服务在线
## 关键指标
- 崩溃原因:`Address already in use`(端口未释放)
- 修复后 RestartSec10 秒
- 正常内存:~1.5GBONNX 模型加载后)
- PID 应保持不变(不反复重启)
- 正常显存:~600MBCUDA 推理CPU fallback 只有 ~200MB
- PID 应保持不变(不反复重启)
- **历史正常态v1**`/health` 返回 `CUDAExecutionProvider`;返回 `CPUExecutionProvider` 是异常
- **当前正常态v22026-09-01 22:00 起4GB 显存笔记本 + llama 7B 共存)**`/health` 返回 `CPUExecutionProvider`(设计选择,腾显存给 llama返回 `CUDAExecutionProvider` 也是合法的(显存 ≥ 6GB 或不跑 llama 的场景)
## ⚠️ 关键状态变化2026-09-01 22:00 → v2 设计)
**新常态**4GB 显存笔记本 + bge-embed + llama 7B 共存场景下,**bge 主动改 CPU 推理**,把 606MB 显存腾给 llama 7B 全 GPU 推理。
- **v1 设计9 月 1 日 22:00 前)**bge 跑 CUDA~600MBllama 跑 CPU9 t/s
- **v2 设计9 月 1 日 22:00 起4GB 显存 + 7B 共存)**bge 跑 CPU~200MBllama 7B 跑 Vulkan GPU**24.7 t/s+175%**`-ngl 27 -fit off`
- **切换方式**:编辑 `/home/muc/.hermes/scripts/bge_embed_server.py``providers=["CUDAExecutionProvider", "CPUExecutionProvider"]` 改成 `providers=["CPUExecutionProvider"]`,重启 `bge-embed`
- **备份**`/home/muc/.hermes/scripts/bge_embed_server.py.bak.gpu` 保留旧 GPU 版本
- **看门狗同步更新**`gpu-health-watchdog.sh` v2 不再把"bge-CPU"当异常(只检查 `/health` 有响应即可),并新增 llama-server-7b 端口检查
- **显存权衡**bge 推理速度从 ~0.5s 降到 ~2-3s单条 embedding但 llama 7B 速度翻倍——综合体验更好
- **触发场景**4GB 显存笔记本上同时跑 bge + llama 7B。如果只跑 bge 或显存 ≥ 6GB建议保持 v1 方案CUDA 推理)
**未来恢复 v1**(如果显存充足或不再跑 llama
```bash
cp /home/muc/.hermes/scripts/bge_embed_server.py.bak.gpu /home/muc/.hermes/scripts/bge_embed_server.py
systemctl --user restart bge-embed
# 同时把 gpu-health-watchdog.sh 的 bge-CPU 异常判断改回
```
## 相关
- `references/cuda-lib-reuse-pattern.md` — ComfyUI CUDA 13 库复用详解
- `references/llama-server-tmp-path-trap.md` — llama-server systemd 路径陷阱(同类问题)
- `../self-healing-infrastructure/references/llama-vulkan-build-guide-20260901.md` — Vulkan 编译 + 4GB 显存约束详解
- `../self-healing-infrastructure/references/systemd-tmpfiles-trap-20260901.md` — /tmp 路径陷阱
> 教训:`/tmp/` 是 volatile tmpfssystemd unit 永远不要写 `/tmp/...` 路径。
> 见 `references/llama-server-tmp-path-trap.md`

View File

@ -1,11 +1,10 @@
# bge-embed 复用 ComfyUI CUDA 13 库2026-09-01 实测)
# CUDA 13 库复用模式 — 4GB 显存笔记本2026-09-01 实测)
> 问题bge-embed 装了 `onnxruntime-gpu``/health` 返回 `CPUExecutionProvider`
> 根因CUDA 13 runtime 库不在 LD_LIBRARY_PATHonnxruntime 加载 CUDA EP 失败
> 场景bge-embed / llama.cpp / 其他需要 CUDA 的本地服务,不想重装 CUDA toolkit。
## 本机配置(不装 CUDA toolkit复用 ComfyUI 的 nvidia 包
## 本机复用模式ComfyUI 已有 CUDA 13
ComfyUI venv 已装好 `nvidia-cu13` + `nvidia-cudnn-cu13`(通过 pip install,路径:
ComfyUI venv 通过 pip 安装了 `nvidia-cu13``nvidia-cudnn-cu13`,路径:
```
/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cu13/lib/
@ -18,7 +17,8 @@ ComfyUI venv 已装好 `nvidia-cu13` + `nvidia-cudnn-cu13`(通过 pip install
/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
├── libcudnn.so.9
├── libcudnn_adv.so.9
└── libcudnn_cnn.so.9
├── libcudnn_cnn.so.9
└── libcudnn_ops.so.9
```
bge-embed systemd service 的 `LD_LIBRARY_PATH` 加这两条:
@ -31,34 +31,52 @@ Environment="LD_LIBRARY_PATH=/home/muc/.local/lib:/usr/local/cuda/lib64:/home/mu
- `nvidia-cu13`、`nvidia-cudnn-cu13` 是 pip 包,解压到 venv 的 site-packages
- CUDA runtime .so 文件在 venv 隔离路径里,系统 `/usr/local/cuda` 不存在也能用
- 只要把 venv 的 nvidia 路径加入 LD_LIBRARY_PATH动态链接器就能找到
- **不需要重装 CUDA toolkit**(省 ~3GB 下载 + 系统级污染)
## 版本对齐规则
| onnxruntime-gpu 版本 | 要求 CUDA | 要求 cuDNN |
|---------------------|-----------|------------|
| 1.19.x | 12.x | 8.x |
| 1.20.x | 12.x | 8.x |
| 1.28.x | 13.x | 9.x |
| 1.29.x | 13.x | 9.x |
本机 ComfyUI 用的是 onnxruntime-gpu 1.28.0,对应 CUDA 13 + cuDNN 9。
## 验证步骤
```bash
# 1. 检查 onnxruntime 能声明 CUDA provider
# 1. onnxruntime 能声明 CUDA provider
LD_LIBRARY_PATH=/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cu13/lib:/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cudnn/lib \
python3 -c "import onnxruntime as ort; print(ort.get_available_providers())"
# 应输出:['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
# 2. 检查实际加载模型时的 provider
# 2. 实际加载模型
LD_LIBRARY_PATH=... python3 -c "
import onnxruntime as ort
sess = ort.InferenceSession('model.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
print(sess.get_providers())
print('实际 providers:', sess.get_providers())
"
# 应输出:['CUDAExecutionProvider', 'CPUExecutionProvider']
# 3. 检查显存占用GPU 推理 ~600MBCPU fallback ~200MB
nvidia-smi --query-gpu=memory.used --format=csv
# 3. 看显存GPU 推理 ~600MB for bge纯 CPU ~200MB
nvidia-smi --query-gpu=memory.used --format=csv,noheader
```
## 通用原则
## 通用公式
- **不用重装 CUDA toolkit**:复用已有 venv 的 nvidia 包更干净
- **版本对齐**onnxruntime-gpu X.Y 需匹配 CUDA X.x + cuDNN 9.x见 https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html
- **快速判断**`curl /health | grep CUDAExecutionProvider` 比看日志更快
**任何需要 CUDA 的 Python 服务**
```bash
# 1. 独立 venv
uv venv ~/.hermes/venvs/<服务名> --python 3.11
VIRTUAL_ENV=~/.hermes/venvs/<服务名> uv pip install onnxruntime-gpu # 或 torch 等
## 相关
# 2. systemd service 加 LD_LIBRARY_PATH
Environment="LD_LIBRARY_PATH=<venv-nvidia-path>:<comfyui-nvidia-path>:<系统-cuda-path>"
- `bge-embed-crash-loop-fix` SKILL.md 根因 C2
- ComfyUI venv: `/home/muc/ComfyUI/venv/`
# 3. 验证 /health + nvidia-smi
```
---
*2026-09-01 | 本地点火修复* — 详见 `bge-embed-crash-loop-fix` 根因 C2

View File

@ -161,6 +161,88 @@ curl -s -X POST "https://api.cnb.cool/<org>/<repo>/-/issues/1/comments" \
- 每 10 分钟跑一次,**monitor 模式**NPC 有进展(评论/PR 变化)才触发汇报,静默零消耗
- 手动立即检查:`python3 ~/.hermes/scripts/cnb_npc_monitor.py`
## ⚠️ 2026-09-01 实战发现:看板集成 + 关键坑
### 通过 Hermes 看板调度 NPC推荐用法
不直接 curl API而是用看板 CLI 脚本:
```bash
# 推荐:路由脚本自动检测 NPC 关键词
python3 ~/.hermes/scripts/kanban-route.py "让 NPC 写个 Flask API" --npc
# → 自动创建仓库(如不存在)+ Issue + 触发评论 + work_mode:true
# 直接调 dispatch 脚本
python3 ~/.hermes/scripts/npc-dispatch.py "写一个函数" --repo muchen-org/test
```
**看板优势**
- 任务有 IDt_xxx可追踪状态
- daemon 自动监控进度
- 失败自动 blocked + diagnostic
- 附件自动归档
### ⚠️ 2026-09-01 实战坑:默认 repo 必须有效
**症状**`kanban-route.py --npc` 不传 `--repo` 时崩 `JSONDecodeError: Expecting value: line 1 column 1 (char 0)`
**根因**`npc-dispatch.py` 默认 repo 是空字符串 → `GET /{repo}` 变成 `GET /` → API 返回 HTML不是 JSON`json.loads()` 崩。
**修复**(已落地):默认 repo 设为 `os.environ.get("CNB_DEFAULT_REPO", "muchen-org/auto-npc")`
**教训**:任何"省略参数"的 API wrapper都要有合理的默认值 + 创建逻辑兜底。
### ⚠️ 2026-09-01 实战NpcProfile 集成到 Hermes
新建了 `~/.hermes/profiles/npc/config.yaml` 让 NPC 走专用 profile
```yaml
model:
provider: sensenova
model: deepseek-v4-flash
worker:
profile: npc
skills: [cnb-codebuddy-npc]
dispatch_command: "python3 ~/.hermes/scripts/npc-dispatch.py"
```
**为什么 profile 配 sensenova 而不是 npc 自己?** 因为 Hermes 看板 dispatch 器需要 model 字段来 spawn worker但 NPC 实际工作由 CNB 平台(不是本地 LLM完成。Profile 的 model 字段是"看板任务的 host 上下文",不影响 NPC 实际执行。
**实践经验**
- t_5a5326fe (research, 55s) ✅
- muchen-org/test-npc PR #2 (NPC, 1m9s) ✅ 4/4 测试通过0 费用
- daemon + gateway dispatcher 冲突 → 加 `--force` 必带
### NPC 完整流程时间线(实测)
| 阶段 | 耗时 | 备注 |
|------|------|------|
| Issue 创建 | < 1s | POST /issues work_mode |
| 触发评论 | < 1s | POST /issues/{n}/comments work_mode:true |
| **排队延迟** | **~3 分钟** | 固定开销 |
| 编码 + 测试 | 30s-2m | 视任务复杂度 |
| 推 PR | < 10s | 自动 |
| **总耗时** | **~5 分钟** | 简单任务;复杂任务可到 10 分钟 |
**与 DSH 对比更新版2026-09-01**
- DSH 现状sensenova 免费额度已用尽HTTP 429
- NPC 现状:完全免费,走 CNB 平台deepseek-v4-flash至 2026-12-31
- **结论**:当前阶段所有可公开项目 → **强烈推荐 NPC**
### NPC 不适合的场景
- 私有项目(含密钥/财务/内部 API
- 需即时结果(< 3 分钟
- 需本地工具调用filesystem / API call
- 需在用户私有环境执行
### 与 kanban 集成时,路由关键词
触发 NPC 路由的关键词(`~/.hermes/scripts/kanban-route.py`
```python
(["npc", "NPC", "云端编码", "CodeBuddy", "cnb", "cnb.cool", "替我上班", "公开项目"], "npc")
```
牧尘在对话中说这些词时,自动路由到 NPC profile。
## API 端点速查swagger 里发现)
| 操作 | 端点 |

View File

@ -0,0 +1,28 @@
# CNB API Quirks — 2026-09-01 实测
## 创建仓库返回空 201 body
**现象**`POST /{org}/-/repos` 返回 HTTP 201但 body 为空(不是 JSON
**正确处理方式**
```python
def api_call(method, path, data=None):
url = f"{API}{path}"
headers = {...}
body = json.dumps(data).encode("utf-8") if data else None
req = urllib.request.Request(url, data=body, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
content = resp.read().decode()
if not content.strip():
return {"status": resp.status, "empty": True}
return json.loads(content)
except urllib.error.HTTPError as e:
print(f"❌ HTTP {e.code}: {e.read().decode()}", file=sys.stderr)
sys.exit(1)
```
## work_mode 字段位置
`work_mode: true` 必须**同时在 Issue 创建和评论触发时都带上**。

View File

@ -1,8 +1,8 @@
---
name: self-healing-infrastructure
description: "自愈基础设施 — 系统监控、配置版本控制、自动回滚、自进化管线、技能管理、自我优化、学习闭环。完整自治体系。牧尘专用。debug铁律函数存在≠真的在工作必须验证文件输出。"
version: 1.26.0
date: 2026-08-30
version: 1.27.0
date: 2026-09-01-v2
author: 小唯 A06
tags: [self-healing, monitoring, auto-rollback, evolution, watchdog, config-protection, daemon, backup, recovery]
category: devops
@ -192,7 +192,7 @@ trigger: 系统部署、开机自启、配置更改、故障恢复场景、备
**daemon.py 外部命令输出陷阱2026-07-25**
`int(外部命令输出)` 必须包 `try/except ValueError` — nvidia-smi / iostat / df 等在驱动/权限/环境异常时输出报错字符串而非数字。详见 `references/feishu-delivery-queue-dns-recovery-20260725.md`
**看门狗/健康检查脚本设计铁律2026-08-12两条 cron 误报排查总结)**
- "看门狗/健康检查脚本设计铁律2026-08-12两条 cron 误报排查总结)"
1. **数据新鲜度必须按各文件真实更新周期检查,不能统一"昨天以内"**stock_daily_health.py 对全部 4 个数据文件要求 1 天新鲜,但 fundamental/sentiment/macro 是周一 08:30 更新、industry_scan 是周五 17:20 更新(周更!)→ 周二起天天误报 STALE。修复`DATA_FILES = {"industry_scan.json": ("行业扫描", 7), ...}` 按文件给 max_age_days`fresh = (d - mtime).days <= max_age_days`。**改任何健康检查前先确认数据源真实更新频率。**
2. **计划时间未到 = NOT_YET 不是 NO_RUN**:健康体检在 18:45 跑,但手动/异常时间跑会把 16:00/18:00/18:30 的 cron 误报"当日未执行"。修复STOCK_CRONS 加计划 HH:MM`now_hhmm < sched_hhmm NOT_YET`不告警)。
3. **"常态离线"必须静默跳过,不是 error**dual-backup.sh 每 6h 硬连局域网 IP 192.168.123.11 报 error——但家庭服务器不在局域网是常态走 frp 域名)。修复:`check_mount()` 返回 2 = 离线常态 → `push_backup` `return 0`(静默),只有"挂载点在但 rsync 失败"才报错。**外部依赖不可达且是已知常态时watchdog 应静默,只在真异常时告警。**
@ -201,7 +201,16 @@ trigger: 系统部署、开机自启、配置更改、故障恢复场景、备
- `sudo mount` 在 cron 无 tty 环境会**等密码卡住** → 必须 `sudo -n`non-interactive立即失败
- 裸 `check_mount; local rc=$?``set -e`check_mount 非零 → 脚本已退出,`local rc=$?` 永不执行 → 必须 `if check_mount; then ... else local rc=$?; fi`if 条件中的命令不受 set -e 影响且能拿到真实返回码)
- 详见 `references/watchdog-freshness-cadence-20260812.md`
- **QUIET=1 静默模式no_agent cron 的"常态不打扰"实现2026-08-12**外部依赖不可达且是常态时如家庭服务器不在局域网cron 每次报 error 会刷屏。实现:`log()` 函数按 `QUIET=1` 只写文件不写 stdoutwrapper 里 `export QUIET=1`;离线分支 `return 0`**空 stdout = 静默,非空 stdout = 投递**);只有真异常才 `return 1` 告警;在线成功才额外 echo 确认。本机 git 快照提到服务器检查之前(本机备份是底线)。详见 `references/watchdog-freshness-cadence-20260812.md`
- **QUIET=1 静默模式no_agent cron 的"常态不打扰"实现2026-08-12**外部依赖不可达且是常态时如家庭服务器不在局域网cron 每次报 error 会刷屏。实现:`log()` 函数按 `QUIET=1` 只写文件不写 stdoutwrapper 里 `export QUIET=1`;离线分支 `return 0`**空 stdout = 静默,非空 stdout = 投递**);只有真异常才 `return 1` 告警;在线成功才额外 echo 确认。本机 git 快照提到服务器检查之前(本机备份是底线)。详见 `references/watchdog-freshness-cadence-20260812.md`
- **2026-09-01 拉现状铁律(牧尘原话"把拉现状刻进骨子里"class-level 教训)**:任何关于"系统/服务/进程/状态"的判断/结论/修复方案,**必须先拉真实状态**terminal 跑命令),绝不用记忆/推断/上下文假设代替。触发条件:① 牧尘问"X 怎么回事/什么状态" ② 准备说"X 是 Y" ③ 准备改/重启/回滚/修任何东西之前 ④ 看到 alarm/服务异常 ⑤ session 重启/失忆/不确定时 ⑥ 出现"应该是/按理说/通常会"等措辞。**反面教材**bge-embed 报"未用 CUDA"→ 我假设"CPU 是 4GB 笔记本正常态" → 改坏了看门狗 → 牧尘纠正"之前都是 gpu" → 实际是装了 `onnxruntime`CPU版而非 `onnxruntime-gpu`,根因是 venv 装错包。看门狗的报警一直是**对的**,是修复方案错。
- **最小命令集**(按需选,不是全跑):`date` / `pgrep -fa` / `ss -tlnp` / `curl /health` / `systemctl --user status` / `journalctl --user -u <svc> -n 20` / `nvidia-smi` / `free -h` / `df -h` / `ls -la` / `head -N`
- **反向约束**(拉现状没做完时禁止):❌ 禁止说"X 应该是好的/通常会/之前是/按设计" ❌ 禁止基于过期 AGENTS.md/SOUL.md/MEMORY 里的状态陈述当前 ❌ 禁止没拉就下"修复方案" ❌ 禁止复用之前的修复脚本而不验证当前真实状态
- **看门狗判断逻辑陷阱class-levelv2 更新 2026-09-01**:看门狗写死的"正常态"必须**有真实运行证据**,不能拍脑袋。**但**"GPU 是正常态"也不是普适铁律——4GB 显存笔记本上同时跑 bge + llama 7B 时,**bge 主动改 CPU 是合理设计选择**(腾显存给 llama不是退化。铁律拉现状 + 了解资源约束 + 验证"为什么这么设计"再下判断。"应该是 X" = 反向信号 = 现在就 curl/grep 验证。**正确说法v19 月 1 日 22:00 前)看门狗报"未用 CUDA"是正确报警v2之后bge-CPU 是预期,不报警——同一个脚本在不同设计阶段合理不同**。详见 `references/gpu-shared-memory-4gb-coexistence-20260901.md`
- **CUDA 库复用模式4GB 显存笔记本2026-09-01 验证)**ComfyUI venv 已装好 `nvidia-cu13` + `nvidia-cudnn-cu13`~600MB CUDA 13 runtime。其他需要 CUDA 的服务bge/llama 量化等)**通过 LD_LIBRARY_PATH 复用**,不需要重装 CUDA toolkit路径 `/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cu13/lib` + `nvidia/cudnn/lib`。通用公式:`<服务> venv` + `pip install onnxruntime-gpu` + `LD_LIBRARY_PATH` 含上面两条 → GPU 推理。验证:`/health` 报 CUDA provider + `nvidia-smi` 看到 ~600MB 显存占用。
- **2026-09-01 llama-server systemd 路径陷阱**llama-server-7b.service 写 `/tmp/llama-vulkan/llama-b10679/llama-server``systemd-tmpfiles-clean.timer` 清 `/tmp` 导致 exit=203/EXEC二进制找不到。修复改用 `/home/muc/.local/bin/llama-server`稳定软链。llama-server-3b 同步修。⚠️ **systemd unit 永远不写 `/tmp/` 路径**。详见 `references/systemd-tmpfiles-trap-20260901.md`
- **2026-09-01 llama.cpp Vulkan 编译 + 4GB 显存约束**:本机 llama.cpp 默认纯 CPU 编译GGML_VULKAN=OFF需重装 `libvulkan-dev` + `glslc` + `spirv-headers` 后重编。⚠️ **4GB 显存跑 7B 模型不够**Xorg(170MB) + bge(606MB) = 776MB剩余 ~3.3GB < 7B Q3 模型 3.6GB 混合模式部分 GPU + KV cache CPU)→ ~12 t/s非全 GPU 25-35 t/s详见 `references/llama-vulkan-build-guide-20260901.md`
- **2026-09-01 bge+llama 共存方案v1→v2 设计切换)**4GB 显存 + bge-embed + llama 7B 同时跑,必须**主动让 bge 改 CPU 推理**`providers=["CPUExecutionProvider"]`),把 606MB 显存腾给 llama让 7B 全 GPU2700MB推理速度从 9 t/s → 14-15 t/s+55%。看门狗逻辑同步bge-CPU 是设计选择不报警。备份 `bge_embed_server.py.bak.gpu` 保留旧版以便回退。详见 `references/gpu-shared-memory-4gb-coexistence-20260901.md`
- **AI Agent 反馈控制方法论2026-08-12 牧尘分享文章消化 + 差距清单)**PEV 循环 / 确定性传感器优先 / "Harness is the Dataset" 离线演化 / HITL 自主度。我们的差距①失败回归闭环缺失learner 缺失败→根因→回写→回归验证)②确定性传感器待补强。详见 `references/agent-feedback-control-methodology-20260812.md`
- **2026-07-20 新增 GitHub API import 方式**Gitea 用户 push 新建仓库会 403`POST /repos/migrate` 从 GitHub URL 直接 import201 创建,返回完整 repo JSON
- `memory-system-self-upgrade.py`**每日4点自升**L7 llm_context.json v2 9字段验证(新增) + 织忆tombstone增长检测+recall_hit健康度 + Soulful清理30天前cares+心迹去重+distilled_rules补充 + TencentDB capture写入验证 + 数据量报告。异常飞书。cron `691709a8b4cf`

View File

@ -0,0 +1,97 @@
# 4GB 显存多 GPU 服务共存方案2026-09-01 实测)
> 场景RTX 3050 Laptop 4GB 显存 + bge-embed + llama.cpp 7B 同时跑。
## 显存分配(实测数据)
```
GPU 总显存: 4096 MiB
├── Xorg / cinnamon / desktop: ~170 MiB
├── bge-embed (Vulkan/CUDA): ~600 MiB
├── llama.cpp 7B (Vulkan GPU 全量): ~2700 MiB
└── 剩余可用: ~626 MiB
```
**4GB 显存装不下 7B Q33.6GB+ bge600MB**。
## 性能取舍矩阵
| 配置 | llama 7B 速度 | bge embedding | 备注 |
|------|-------------|---------------|------|
| 双方都跑 GPU不现实OOM| — | — | 4GB 显存装不下,会 fallback |
| bge GPU + llama CPU | 9 t/s | 0.5s | v1 默认 |
| **bge CPU + llama GPU推荐** | **24-25 t/s**27/29 层全 GPU`-ngl 27 -fit off` | 2-3s | **v2 平衡方案** |
| bge CPU + llama 半 GPU | 12 t/s | 2-3s | 旧状态(已淘汰) |
| 都跑 CPU | 9 t/s | 2-3s | 退化方案 |
## 切换步骤v1 → v2
```bash
# 1. 备份当前 bge 脚本
cp /home/muc/.hermes/scripts/bge_embed_server.py /home/muc/.hermes/scripts/bge_embed_server.py.bak.gpu
# 2. 改 providers 列表
sed -i 's/providers=\["CUDAExecutionProvider", "CPUExecutionProvider"\]/providers=["CPUExecutionProvider"]/' \
/home/muc/.hermes/scripts/bge_embed_server.py
# 3. 重启
systemctl --user restart bge-embed
sleep 10
curl -s http://localhost:8000/health # 应含 "CPUExecutionProvider"
# 4. llama-server 重启释放 + 重新加载更多层到 GPU
systemctl --user restart llama-server-7b
sleep 15
nvidia-smi --query-gpu=memory.used --format=csv,noheader # 应 ~2700MB
# 5. 测速度
time curl -s -X POST http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"local","messages":[{"role":"user","content":"写诗"}],"max_tokens":100}'
# 应 ~7-8s (14-15 t/s)
```
## 关键诊断
```bash
# 1. 看 llama 是否真的全 GPU
nvidia-smi --query-gpu=memory.used --format=csv,noheader
# 全 GPU: 2700-3000 MiB
# 混合模式: 1500-2000 MiB部分层 CPU
# 2. 看 7B prompt 处理 vs token 生成速度
journalctl --user -u llama-server-7b --no-pager -n 50 | grep "print_timing"
# prompt eval 200+ t/s = GPU OK
# token generation 10-15 t/s = 全 GPU 模式(受限于 4GB 显存)
# 3. 看 bge 状态
curl -s http://localhost:8000/health | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['providers'])"
# v2 设计: ['CPUExecutionProvider'] ← 这是正常的,不要慌
```
## 决策树
```
4GB 显存笔记本 + bge + llama 同时跑?
├─ 是 → bge 改 CPU (v2)llama 全 GPU 推理,~14 t/s
├─ 否,只跑 bge → bge 用 CUDA 推理 (v1)~600MB 显存,~0.5s/embedding
├─ 否,只跑 llama → llama 全 GPU 推理(无 bge 抢显存)
└─ 显存 ≥ 6GB → bge + llama 都可以全 GPU
```
## 常见陷阱
1. **bge 改 CPU 之后看门狗报"未用 CUDA"** — 这是**正常状态**,不是异常。看门狗 v2 不再把"bge-CPU"当异常。
2. **改完 bge 显存没释放** — 必须 `systemctl --user restart bge-embed`,不能只改脚本不重启
3. **llama 重启后显存仍是混合模式** — 7B 全量超过 4GB - 系统占用,部分层必然在 CPU
4. **Vulkan 不工作** — 编译时漏装 `libvulkan-dev` + `glslc` + `spirv-headers` 三个包,必报错
## 相关 references
- `llama-vulkan-build-guide-20260901.md` — Vulkan 编译细节
- `systemd-tmpfiles-trap-20260901.md` — /tmp 路径陷阱(同类问题)
- `../bge-embed-crash-loop-fix/SKILL.md` — bge 服务管理
- `../bge-embed-crash-loop-fix/references/cuda-lib-reuse-pattern.md` — CUDA 13 库复用(如果想切回 v1
---
*2026-09-01 | v1→v2 设计切换验证*

View File

@ -0,0 +1,117 @@
# llama.cpp `-ngl N -fit off` — 4GB 显存装 7B 全 GPU 推理2026-09-01 实测,**反"显存不够"说法**
> **核心结论**RTX 3050 Laptop 4GB 显存能装下 7B Q3_K_M 全 GPU 推理24-25 t/s方法是 `-ngl 27 -fit off --ctx-size 2048 --parallel 1`**不是"装不下只能 14 t/s"**。
## 反面教材2026-09-01 我犯的错)
我说"4GB 显存装不下 7B 全 GPU建议 3B 模型或接受 14 t/s"——**错的**。牧尘纠正后实测:
```bash
# 我之前的"诊断"
nvidia-smi --query-gpu=memory.used # 显示 2700 MiB → 推断混合模式 → 给 12-14 t/s 结论
# 真实情况
journalctl --user -u llama-server-7b -n 50 | grep "memory breakdown"
# | Vulkan0 (RTX 3050 Laptop GPU) | 4096 = 57 + (3460 = 3182 + 104 + 173) + 578 |
# 3182 MiB 模型 + 104 MiB KV cache + 173 MiB compute buffer
# "只有 14 t/s" 是因为 auto-fit 把 28 层装不下 → fallback CPU 2 层 → 部分计算 CPU
```
**根因**llama.cpp `common_params_fit_impl` 自动 fit 时,会预留 1024 MiB 显存 buffer"free memory target"),导致 model layer 装不下,只能 offload 一部分到 CPU。手动 `-ngl 27` + `-fit off` 跳过这个预留27/29 层全 GPU。
## 实际可用的参数组合4GB + bge-CPU 模式)
```bash
ExecStart=/home/muc/.local/bin/llama-server \
-m /home/muc/models/Qwen-Qwen2.5-7B-Instruct-GGUF/qwen2.5-7b-instruct-q3_k_m.gguf \
--host 127.0.0.1 --port 8080 \
--ctx-size 2048 \ # KV cache ~100 MiB
--parallel 1 \ # 1 slot不要 parallel=2
-ngl 27 \ # 27/29 层 GPU不是 -ngl 99 也不是 auto
-fit off # 跳过 auto-fit 的 1024 MiB 预留
```
## 为什么不是 -ngl 99
`common_params_fit_impl``-ngl 99`(或 auto下报的错
```
projected to use 3641 MiB of device memory vs. 3522 MiB of free device memory
cannot meet free memory target of 1024 MiB, need to reduce device memory by 1143 MiB
```
它默认要留 1024 MiB buffer防止 OOM实际可用 3522 MiB 不够 3641 MiB 总需求。`-fit off` 跳过这个目标,按真实需求分配。
## 显存分账(实测)
```
GPU 总: 4096 MiB
├── Xorg/cinnamon/firefox: ~175 MiB
├── llama-server 进程:
│ ├── 模型权重 (27 层 GPU): 3182 MiB
│ ├── KV cache (ctx=2048): 104 MiB
│ ├── compute buffer: 173 MiB
│ └── 实际 GPU 占用: 3459 MiB
└── 剩余 free: ~462 MiB
→ 27/29 层全 GPU ✓
```
## 速度对比200 token 测试)
| 配置 | 速度 | 备注 |
|------|------|------|
| -ngl 99 (auto-fit 失败 → fallback 2 层 CPU) | 12-14 t/s | 之前的状态 |
| **-ngl 27 -fit off (27/29 层 GPU)** | **24.6 t/s** | **正确配置** |
| -ngl 29 (全 29 层 GPU) | OOM | compute buffer 装不下 |
**提升 1.7-2x**。
## systemd unit 模板(写入 llama-server-7b.service
```ini
[Service]
Type=simple
ExecStart=/home/muc/.local/bin/llama-server \
-m /home/muc/models/Qwen-Qwen2.5-7B-Instruct-GGUF/qwen2.5-7b-instruct-q3_k_m.gguf \
--host 127.0.0.1 --port 8080 \
--ctx-size 2048 --parallel 1 -ngl 27 -fit off
Restart=on-failure
RestartSec=10
```
## 诊断"是不是全 GPU 模式"
```bash
# 1. 看启动日志
journalctl --user -u llama-server-7b -n 50 | grep -E "offloading|memory breakdown|load_tensors"
# 应看到: offloading 27 repeating layers to GPU
# | - Vulkan0 | 4096 = 57 + (3460 = 3182 + 104 + 173) + ... |
# 2. 推理时 print_timing
journalctl --user -u llama-server-7b -n 20 | grep "print_timing"
# eval time 应该 ~40 ms/tok (= 25 t/s)
# prompt eval 应该 ~2 ms/tok (= 500 t/s)
# 3. 测速度
time curl -s -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"local","messages":[{"role":"user","content":"x"}],"max_tokens":100}' | head -c 50
# 全 GPU 模式: ~4s25 t/s
# 混合模式: ~8-12s10-14 t/s
```
## 反面教材原因(写给未来的我)
1. **不要被 llama-server 的 nvidia-smi 显存数骗了**2700 MiB 显存 ≠ 全 GPU可能是 auto-fit 失败的混合模式
2. **不要相信"4GB 装不下 7B"的网络结论**:那是 8-10 年前的过时说法。Q3_K_M 量化 + 现代 Vulkan + `-fit off` 完全可以
3. **要算"显存账"**:模型权重 + KV cache + compute buffer 三者都要算
4. **要看 print_timing 日志**而不是只看显存prompt eval vs eval time 比例能直接判断是否真 GPU
## 相关
- `llama-vulkan-build-guide-20260901.md` — Vulkan 编译(这之前的指南说"4GB 装不下",是错的,看本文件)
- `gpu-shared-memory-4gb-coexistence-20260901.md` — bge+llama 共存bge-CPU 仍是对的,但 llama 速度应该是 25 t/s 不是 14 t/s
---
*2026-09-01 | 牧尘纠正"4g显存可以装7b是你的方式不对"后实测验证*

View File

@ -0,0 +1,98 @@
# llama-server Vulkan GPU 推理 — 编译指南2026-09-01 实测)
> 教训:本机 llama-server 默认是纯 CPU 编译,需重新编译开启 `-DGGML_VULKAN=ON`
## 检查当前版本
```bash
# 1. ldd 看是否有 vulkan 依赖
ldd /home/muc/.local/bin/llama-server | grep -iE "vulkan|ggml"
# 2. 启动日志看 backend
journalctl --user -u llama-server-7b --no-pager -n 50 | grep -iE "vulkan|gpu|backend"
# 3. 看显存GPU 推理应 ~2700MB纯 CPU 只 ~835MB
nvidia-smi --query-gpu=memory.used --format=csv,noheader
```
## 编译步骤
```bash
# 1. 安装依赖
sudo apt-get install -y libvulkan-dev glslc spirv-headers
# 2. 配置(先清 CMakeCache
cd /home/muc/llama.cpp
rm -rf build
cmake -B build -DGGML_VULKAN=ON -DGGML_CUDA=OFF -DCMAKE_BUILD_TYPE=Release
# 3. 编译(只编 server
cmake --build build --target llama-server -j$(nproc)
# 4. 替换(先停服务)
systemctl --user stop llama-server-7b llama-server
cp /home/muc/llama.cpp/build/bin/llama-server /home/muc/.local/bin/llama-server
systemctl --user start llama-server-7b
# 5. 验证
sleep 15
nvidia-smi --query-gpu=memory.used --format=csv,noheader # 应 ~2700MB
journalctl --user -u llama-server-7b -n 20 | grep -iE "vulkan|gpu"
```
## 关键坑
1. **必须先 `rm -rf build`**CMakeCache 缓存旧设置,不清干净重新 cmake 不会生效
2. **glslc 必须装**`libvulkan-dev` 不够,还需要 `glslc`SPIR-V 编译器)和 `spirv-headers`
3. **llama.cpp 必须用 build 目录下的**`/home/muc/llama.cpp/build/bin/llama-server`
4. **系统软链**`/home/muc/.local/bin/llama-server` 软链到 build 目录stable
## 性能对比
| 后端 | 显存占用 | 推理速度 |
|------|---------|---------|
| Vulkan GPU全模型在 GPU | ~2700 MB | ~25-35 t/s |
| 混合模式GPU + KV cache CPU | ~2700 MB | ~12 t/s |
| 纯 CPU | ~835 MB (bge only) | ~9 t/s |
**⚠️ 4GB 显存笔记本内存约束2026-09-01 实测,**v2 修正****
之前指南说"4GB 装不下 7B 全 GPU 只能混合模式 12 t/s"——**错的**。正确方法见 `references/llama-ngl-fit-off-4gb-fix-20260901.md``-ngl 27 -fit off --ctx-size 2048 --parallel 1` 即可让 27/29 层全 GPU → 24.6 t/s。
RTX 3050 Laptop 4GB 显存实测账auto-fit 会预留 1024 MiB 失败;`-fit off` 跳过):
实测数据GPU 总共 4096 MiB
```
Xorg/cinnamon/desktop: ~170 MiB
bge-embed (Vulkan): ~606 MiB
剩余可用: ~3.3 GB
7B Q3_K_M 模型权重: ~3.6 GB ← 不够!
```
结果:部分层在 GPUKV cache 在 CPU混合模式→ 生成速度仅 ~12 t/s而非全 GPU 的 25-35 t/s
### 解决方案
| 方案 | 效果 | 代价 |
|------|------|------|
| bge 改 CPU | 省 606 MiB → 7B 全 GPU → ~25 t/s | bge 推理慢 2-3x |
| 换 3B 模型 | 全 GPU → ~30-50 t/s | 模型质量下降 |
| 接受现状 | 12 t/sprompt 快生成慢 | 当前状态 |
### 诊断方法
```bash
# 1. 看 GPU 占用(判断是否全模型在 GPU
nvidia-smi --query-gpu=memory.used --format=csv,noheader
# 正常7B 全 GPU2700-3000 MiB
# 偏少(混合):< 2500 MiB
# 2. 测真实推理速度200 tokens 需多久)
time curl -s -X POST http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"local","messages":[{"role":"user","content":"x"}],"max_tokens":200}'
# 全 GPU~8-10s混合~16-20s纯 CPU~30-40s
```
---
*2026-09-01 | 本地点火修复*

View File

@ -0,0 +1,62 @@
# systemd-tmpfiles-clean 陷阱 — /tmp 路径不稳定2026-09-01 实测)
> 教训systemd unit 文件里写 `/tmp/...` 路径,会被 `systemd-tmpfiles-clean.timer`(默认每 10 天或启动时清掉导致服务启动失败exit=203/EXEC
## 症状
```
systemctl --user status llama-server-7b
Active: activating (auto-restart) (Result: exit-code) ... code=exited, status=203/EXEC
journalctl -u llama-server-7b
ExecStart=/tmp/llama-vulkan/llama-b10679/llama-server ... (code=exited, status=203/EXEC)
```
**status=203/EXEC** = 找不到 ExecStart 指定的二进制文件。
## 根因
```bash
# systemd-tmpfiles-clean.timer 默认触发点
systemctl list-timers --all | grep tmpfiles
# → systemd-tmpfiles-clean.timer (每 10 天或启动时)
```
**`/tmp/` 是 volatile tmpfs重启或定时清理后内容消失。**
## 排查
```bash
# 1. 确认 binary 确实不在
ls -la /tmp/llama-vulkan/llama-b10679/llama-server
# 2. 确认 timer
systemctl list-timers --all | grep tmpfiles
# 3. 找替代路径
find / -name "llama-server" -type f 2>/dev/null
```
## 修复
把 systemd unit 里的 `/tmp/...` 路径改成**持久路径**
```bash
# 错误(会被清)
ExecStart=/tmp/llama-vulkan/llama-b10679/llama-server ...
# 正确(持久)
ExecStart=/home/muc/.local/bin/llama-server ...
# 或
ExecStart=/home/muc/llama.cpp/build/bin/llama-server ...
```
## 预防
**任何 systemd unit 文件的 ExecStart 路径必须:**
1. 在持久文件系统上(`/home/`、`/usr/local/bin/`、`~/.local/bin/` 等)
2. **绝不**在 `/tmp/`、`/var/tmp/`、`$HOME/.cache/` 下
3. 用绝对路径,不要用相对路径
---
*2026-09-01*