auto-snapshot 2026-09-01 23:22:39
This commit is contained in:
parent
7709fe1c56
commit
5d3b64de0c
25
AGENTS.md
25
AGENTS.md
|
|
@ -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()))"
|
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。
|
- **当前设计**:bge 跑 CPU,llama 7B 跑 GPU(共享 4GB 显存)
|
||||||
- **不要接受 CPU fallback**:如果 `/health` 返回 `CPUExecutionProvider`,说明 onnxruntime 装错了(应装 `onnxruntime-gpu`)或 CUDA 库路径不对
|
- **为什么改 CPU**:bge 调用频率低(织忆 recall),CPU 推理够用,腾显存给 llama 让 7B 全 GPU
|
||||||
- **CUDA 13 库复用**:这台机器 ComfyUI venv 已装好 `nvidia/cu13` + `nvidia/cudnn`,bge 通过 LD_LIBRARY_PATH 复用,不需要重装 CUDA toolkit
|
- **历史变化**:
|
||||||
- **venv 路径**:`/home/muc/.hermes/venvs/bge-embed/`(独立,**不污染** hermes 本体)
|
- v1:bge 跑 GPU(CUDA),llama 跑 CPU → llama 太慢(9 t/s)
|
||||||
- 详见 skill: `bge-embed-crash-loop-fix`(根因 C:venv 缺依赖 / 用错 onnxruntime)
|
- v2(22:00 起):bge 改 CPU,llama 改 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` 丢失时)
|
### 重建路径(`/tmp/memoryweave` 丢失时)
|
||||||
|
|
||||||
|
|
|
||||||
42
SOUL.md
42
SOUL.md
|
|
@ -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:任何"它是不是坏了"问题
|
#### 触发方式 C:任何"它是不是坏了"问题
|
||||||
|
|
||||||
| 看到关键词 | 立刻拉状态 |
|
| 看到关键词 | 立刻拉状态 |
|
||||||
|
|
|
||||||
222
config.yaml
222
config.yaml
|
|
@ -140,41 +140,41 @@ checkpoints:
|
||||||
code_execution:
|
code_execution:
|
||||||
mode: project
|
mode: project
|
||||||
command_allowlist:
|
command_allowlist:
|
||||||
- script execution via heredoc
|
- script execution via heredoc
|
||||||
- shell command via -c/-lc flag
|
- shell command via -c/-lc flag
|
||||||
- delete in root path
|
- delete in root path
|
||||||
- overwrite system file via tee
|
- overwrite system file via tee
|
||||||
- stop/restart system service
|
- stop/restart system service
|
||||||
- pipe remote content to shell
|
- pipe remote content to shell
|
||||||
- sudo with combined-flag privilege escalation
|
- sudo with combined-flag privilege escalation
|
||||||
- kill process via pgrep expansion (self-termination)
|
- kill process via pgrep expansion (self-termination)
|
||||||
- execute_code
|
- execute_code
|
||||||
- overwrite system file via redirection
|
- overwrite system file via redirection
|
||||||
- copy/move file into /etc/
|
- copy/move file into /etc/
|
||||||
- kill hermes/gateway process (self-termination)
|
- kill hermes/gateway process (self-termination)
|
||||||
- overwrite system config
|
- overwrite system config
|
||||||
- overwrite project env/config via redirection
|
- overwrite project env/config via redirection
|
||||||
- in-place edit of system config
|
- in-place edit of system config
|
||||||
- stop/restart hermes gateway (kills running agents)
|
- stop/restart hermes gateway (kills running agents)
|
||||||
- shell execution via heredoc
|
- shell execution via heredoc
|
||||||
- copy/move file into sensitive credential/SSH/shell-rc path
|
- copy/move file into sensitive credential/SSH/shell-rc path
|
||||||
- force kill processes (killall -KILL)
|
- force kill processes (killall -KILL)
|
||||||
- find -delete
|
- find -delete
|
||||||
- in-place edit of Hermes config/env
|
- in-place edit of Hermes config/env
|
||||||
- overwrite project env/config file
|
- overwrite project env/config file
|
||||||
- script execution via -e/-c flag
|
- script execution via -e/-c flag
|
||||||
- start gateway outside systemd (use 'systemctl --user restart hermes-gateway')
|
- start gateway outside systemd (use 'systemctl --user restart hermes-gateway')
|
||||||
- git force push (rewrites remote history)
|
- git force push (rewrites remote history)
|
||||||
- disk copy
|
- disk copy
|
||||||
- hermes update (restarts gateway, kills running agents)
|
- hermes update (restarts gateway, kills running agents)
|
||||||
- recursive delete
|
- recursive delete
|
||||||
- command parser limit or malformed executable payload
|
- command parser limit or malformed executable payload
|
||||||
- SQL TRUNCATE
|
- SQL TRUNCATE
|
||||||
- copy/move file into system config path
|
- copy/move file into system config path
|
||||||
- sudo with privilege flag (stdin/askpass/shell/list)
|
- sudo with privilege flag (stdin/askpass/shell/list)
|
||||||
- world/other-writable permissions
|
- world/other-writable permissions
|
||||||
- force kill processes
|
- force kill processes
|
||||||
- git force push short flag (rewrites remote history)
|
- git force push short flag (rewrites remote history)
|
||||||
compression:
|
compression:
|
||||||
abort_on_summary_failure: false
|
abort_on_summary_failure: false
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
@ -192,7 +192,7 @@ cron:
|
||||||
gateway_required: true
|
gateway_required: true
|
||||||
model: glm-4-flash
|
model: glm-4-flash
|
||||||
model_provider: zhipu
|
model_provider: zhipu
|
||||||
provider: auto
|
provider: ''
|
||||||
wrap_response: true
|
wrap_response: true
|
||||||
curator:
|
curator:
|
||||||
archive_after_days: 90
|
archive_after_days: 90
|
||||||
|
|
@ -259,9 +259,9 @@ display:
|
||||||
resume_skip_tool_only: true
|
resume_skip_tool_only: true
|
||||||
runtime_footer:
|
runtime_footer:
|
||||||
fields:
|
fields:
|
||||||
- model
|
- model
|
||||||
- context_pct
|
- context_pct
|
||||||
- cwd
|
- cwd
|
||||||
show_cost: false
|
show_cost: false
|
||||||
show_reasoning: false
|
show_reasoning: false
|
||||||
skin: default
|
skin: default
|
||||||
|
|
@ -277,14 +277,14 @@ display:
|
||||||
first_lines: 2
|
first_lines: 2
|
||||||
last_lines: 2
|
last_lines: 2
|
||||||
fallback_providers:
|
fallback_providers:
|
||||||
- provider: agnes
|
- provider: agnes
|
||||||
model: agnes-2.0-flash
|
model: agnes-2.0-flash
|
||||||
- provider: zhipu
|
- provider: zhipu
|
||||||
model: glm-4-flash
|
model: glm-4-flash
|
||||||
- provider: sensenova
|
- provider: sensenova
|
||||||
model: deepseek-v4-flash
|
model: deepseek-v4-flash
|
||||||
- provider: opencode-free
|
- provider: opencode-free
|
||||||
model: x-preview-f-free
|
model: x-preview-f-free
|
||||||
file_read_max_chars: 100000
|
file_read_max_chars: 100000
|
||||||
gateway:
|
gateway:
|
||||||
media_delivery_allow_dirs: []
|
media_delivery_allow_dirs: []
|
||||||
|
|
@ -312,7 +312,7 @@ kanban:
|
||||||
worker_log_rotate_bytes: 2097152
|
worker_log_rotate_bytes: 2097152
|
||||||
known_plugin_toolsets:
|
known_plugin_toolsets:
|
||||||
cli:
|
cli:
|
||||||
- spotify
|
- spotify
|
||||||
logging:
|
logging:
|
||||||
backup_count: 3
|
backup_count: 3
|
||||||
level: INFO
|
level: INFO
|
||||||
|
|
@ -339,16 +339,16 @@ mcp_servers:
|
||||||
enabled: true
|
enabled: true
|
||||||
codegraph:
|
codegraph:
|
||||||
args:
|
args:
|
||||||
- serve
|
- serve
|
||||||
- --mcp
|
- --mcp
|
||||||
command: codegraph
|
command: codegraph
|
||||||
connect_timeout: 60
|
connect_timeout: 60
|
||||||
enabled: true
|
enabled: true
|
||||||
timeout: 120
|
timeout: 120
|
||||||
dbx:
|
dbx:
|
||||||
args:
|
args:
|
||||||
- -y
|
- -y
|
||||||
- '@dbx-app/mcp-server'
|
- '@dbx-app/mcp-server'
|
||||||
command: npx
|
command: npx
|
||||||
connect_timeout: 60
|
connect_timeout: 60
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
@ -359,8 +359,8 @@ mcp_servers:
|
||||||
url: https://mcp.firecrawl.dev/v2/mcp
|
url: https://mcp.firecrawl.dev/v2/mcp
|
||||||
github:
|
github:
|
||||||
args:
|
args:
|
||||||
- -y
|
- -y
|
||||||
- '@modelcontextprotocol/server-github'
|
- '@modelcontextprotocol/server-github'
|
||||||
command: npx
|
command: npx
|
||||||
enabled: true
|
enabled: true
|
||||||
env:
|
env:
|
||||||
|
|
@ -368,8 +368,8 @@ mcp_servers:
|
||||||
timeout: 60
|
timeout: 60
|
||||||
openclaw:
|
openclaw:
|
||||||
args:
|
args:
|
||||||
- mcp
|
- mcp
|
||||||
- serve
|
- serve
|
||||||
command: openclaw
|
command: openclaw
|
||||||
connect_timeout: 60
|
connect_timeout: 60
|
||||||
timeout: 300
|
timeout: 300
|
||||||
|
|
@ -390,12 +390,12 @@ moa:
|
||||||
fanout: user_turn
|
fanout: user_turn
|
||||||
reference_max_tokens: 800
|
reference_max_tokens: 800
|
||||||
reference_models:
|
reference_models:
|
||||||
- model: openai/gpt-oss-120b
|
- model: openai/gpt-oss-120b
|
||||||
provider: newapi-local
|
provider: newapi-local
|
||||||
- model: nvidia/llama-3.3-nemotron-super-49b-v1
|
- model: nvidia/llama-3.3-nemotron-super-49b-v1
|
||||||
provider: newapi-local
|
provider: newapi-local
|
||||||
- model: mistralai/mistral-large-3-675b-instruct-2512
|
- model: mistralai/mistral-large-3-675b-instruct-2512
|
||||||
provider: newapi-local
|
provider: newapi-local
|
||||||
save_traces: false
|
save_traces: false
|
||||||
model:
|
model:
|
||||||
api_key_env: ''
|
api_key_env: ''
|
||||||
|
|
@ -422,23 +422,23 @@ paste_collapse_threshold: 5
|
||||||
paste_collapse_threshold_fallback: 5
|
paste_collapse_threshold_fallback: 5
|
||||||
platform_toolsets:
|
platform_toolsets:
|
||||||
cli:
|
cli:
|
||||||
- browser
|
- browser
|
||||||
- clarify
|
- clarify
|
||||||
- code_execution
|
- code_execution
|
||||||
- computer_use
|
- computer_use
|
||||||
- cronjob
|
- cronjob
|
||||||
- delegation
|
- delegation
|
||||||
- file
|
- file
|
||||||
- image_gen
|
- image_gen
|
||||||
- memory
|
- memory
|
||||||
- session_search
|
- session_search
|
||||||
- skills
|
- skills
|
||||||
- terminal
|
- terminal
|
||||||
- todo
|
- todo
|
||||||
- tts
|
- tts
|
||||||
- vision
|
- vision
|
||||||
- web
|
- web
|
||||||
- mcp-codegraph
|
- mcp-codegraph
|
||||||
firecrawl:
|
firecrawl:
|
||||||
connect_timeout: 30
|
connect_timeout: 30
|
||||||
timeout: 120
|
timeout: 120
|
||||||
|
|
@ -456,9 +456,9 @@ platforms:
|
||||||
plugins:
|
plugins:
|
||||||
disabled: []
|
disabled: []
|
||||||
enabled:
|
enabled:
|
||||||
- agentic-sms-gateway
|
- agentic-sms-gateway
|
||||||
- cronalytics
|
- cronalytics
|
||||||
- rtk-rewrite
|
- rtk-rewrite
|
||||||
prefill_messages_file: ''
|
prefill_messages_file: ''
|
||||||
privacy:
|
privacy:
|
||||||
redact_pii: false
|
redact_pii: false
|
||||||
|
|
@ -473,8 +473,8 @@ providers:
|
||||||
default_model: agnes-2.0-flash
|
default_model: agnes-2.0-flash
|
||||||
key_env: AGNES_API_KEY
|
key_env: AGNES_API_KEY
|
||||||
models:
|
models:
|
||||||
- agnes-2.0-flash
|
- agnes-2.0-flash
|
||||||
- agnes-2.5-flash
|
- agnes-2.5-flash
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 60
|
timeout: 60
|
||||||
deepseek:
|
deepseek:
|
||||||
|
|
@ -483,8 +483,8 @@ providers:
|
||||||
default_model: deepseek-v4-flash
|
default_model: deepseek-v4-flash
|
||||||
key_env: DEEPSEEK_API_KEY
|
key_env: DEEPSEEK_API_KEY
|
||||||
models:
|
models:
|
||||||
- deepseek-v4-flash
|
- deepseek-v4-flash
|
||||||
- deepseek-v4-pro
|
- deepseek-v4-pro
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 30
|
timeout: 30
|
||||||
mimo:
|
mimo:
|
||||||
|
|
@ -493,8 +493,8 @@ providers:
|
||||||
default_model: mimo-v2.5-pro
|
default_model: mimo-v2.5-pro
|
||||||
key_env: XIAOMI_API_KEY
|
key_env: XIAOMI_API_KEY
|
||||||
models:
|
models:
|
||||||
- mimo-v2.5-pro
|
- mimo-v2.5-pro
|
||||||
- mimo-v2.5
|
- mimo-v2.5
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 60
|
timeout: 60
|
||||||
newapi-local:
|
newapi-local:
|
||||||
|
|
@ -503,10 +503,10 @@ providers:
|
||||||
cost_factor: 0.0
|
cost_factor: 0.0
|
||||||
default_model: agnes-2.0-flash
|
default_model: agnes-2.0-flash
|
||||||
models:
|
models:
|
||||||
- nvidia/nemotron-mini-4b-instruct
|
- nvidia/nemotron-mini-4b-instruct
|
||||||
- openai/gpt-oss-120b
|
- openai/gpt-oss-120b
|
||||||
- nvidia/nvidia-nemotron-nano-9b-v2
|
- nvidia/nvidia-nemotron-nano-9b-v2
|
||||||
- meta/llama-3.1-8b-instruct
|
- meta/llama-3.1-8b-instruct
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 30
|
timeout: 30
|
||||||
omniroute-local:
|
omniroute-local:
|
||||||
|
|
@ -515,9 +515,9 @@ providers:
|
||||||
cost_factor: 0.0
|
cost_factor: 0.0
|
||||||
default_model: auto/chat
|
default_model: auto/chat
|
||||||
models:
|
models:
|
||||||
- auto/chat
|
- auto/chat
|
||||||
- auto/best-free
|
- auto/best-free
|
||||||
- auto/coding
|
- auto/coding
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 30
|
timeout: 30
|
||||||
opencode-free:
|
opencode-free:
|
||||||
|
|
@ -526,12 +526,12 @@ providers:
|
||||||
cost_factor: 0.0
|
cost_factor: 0.0
|
||||||
default_model: x-preview-f-free
|
default_model: x-preview-f-free
|
||||||
models:
|
models:
|
||||||
- x-preview-f-free
|
- x-preview-f-free
|
||||||
- hy3-free
|
- hy3-free
|
||||||
- laguna-s-2.1-free
|
- laguna-s-2.1-free
|
||||||
- nemotron-3-ultra-free
|
- nemotron-3-ultra-free
|
||||||
- nemotron-3.5-lightning-free
|
- nemotron-3.5-lightning-free
|
||||||
- muse-spark-1.2-contributor-free
|
- muse-spark-1.2-contributor-free
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 30
|
timeout: 30
|
||||||
sensenova:
|
sensenova:
|
||||||
|
|
@ -540,18 +540,18 @@ providers:
|
||||||
cost_factor: 0.0
|
cost_factor: 0.0
|
||||||
default_model: deepseek-v4-flash
|
default_model: deepseek-v4-flash
|
||||||
models:
|
models:
|
||||||
- deepseek-v4-flash
|
- deepseek-v4-flash
|
||||||
- glm-5.2
|
- glm-5.2
|
||||||
- sensenova-6.8-flash-lite
|
- sensenova-6.8-flash-lite
|
||||||
rate_limit: 1000
|
rate_limit: 1000
|
||||||
timeout: 30
|
timeout: 30
|
||||||
zhipu:
|
zhipu:
|
||||||
api_key: ${ZHIPU_API_KEY}
|
api_key: ${ZHIPU_API_KEY}
|
||||||
base_url: https://open.bigmodel.cn/api/paas/v4
|
base_url: https://open.bigmodel.cn/api/paas/v4
|
||||||
models:
|
models:
|
||||||
- glm-4-flash
|
- glm-4-flash
|
||||||
- glm-4-air
|
- glm-4-air
|
||||||
- glm-4-long
|
- glm-4-long
|
||||||
type: openai
|
type: openai
|
||||||
llama-local:
|
llama-local:
|
||||||
api_key: local-key
|
api_key: local-key
|
||||||
|
|
@ -559,8 +559,8 @@ providers:
|
||||||
cost_factor: 0.0
|
cost_factor: 0.0
|
||||||
default_model: qwen3b
|
default_model: qwen3b
|
||||||
models:
|
models:
|
||||||
- qwen3b
|
- qwen3b
|
||||||
- qwen7b
|
- qwen7b
|
||||||
rate_limit: 100
|
rate_limit: 100
|
||||||
timeout: 300
|
timeout: 300
|
||||||
context_length: 131072
|
context_length: 131072
|
||||||
|
|
@ -673,8 +673,8 @@ tools:
|
||||||
search_default_limit: 5
|
search_default_limit: 5
|
||||||
threshold_pct: 10
|
threshold_pct: 10
|
||||||
toolsets:
|
toolsets:
|
||||||
- hermes-cli
|
- hermes-cli
|
||||||
- web
|
- web
|
||||||
tts:
|
tts:
|
||||||
edge:
|
edge:
|
||||||
voice: zh-CN-XiaoyiNeural
|
voice: zh-CN-XiaoyiNeural
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ sess_options.enable_mem_pattern = False # 禁用内存模式,避免碎片
|
||||||
session = ort.InferenceSession(
|
session = ort.InferenceSession(
|
||||||
os.path.join(MODEL_PATH, MODEL_FILE),
|
os.path.join(MODEL_PATH, MODEL_FILE),
|
||||||
sess_options=sess_options,
|
sess_options=sess_options,
|
||||||
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
|
providers=["CPUExecutionProvider"],
|
||||||
)
|
)
|
||||||
log.info("ONNX 模型就绪 — providers=%s", session.get_providers())
|
log.info("ONNX 模型就绪 — providers=%s", session.get_providers())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -2,20 +2,39 @@
|
||||||
# GPU 健康看门狗 — 监控 bge-embed 服务 + 显存 + 磁盘
|
# GPU 健康看门狗 — 监控 bge-embed 服务 + 显存 + 磁盘
|
||||||
# 正常静默,异常报警
|
# 正常静默,异常报警
|
||||||
#
|
#
|
||||||
# 设计说明(2026-09-01 更新):
|
# 设计说明(2026-09-01 更新 v2):
|
||||||
# bge-m3 用 onnxruntime-gpu + CUDAExecutionProvider 推理;显存耗用 ~600MB。
|
#
|
||||||
# 如果 /health 返回的是 CPUExecutionProvider 而非 CUDAExecutionProvider,
|
# GPU 4GB 笔记本 + bge-embed + llama 7B 共存。优先级:
|
||||||
# 说明 onnxruntime CUDA provider 加载失败(常见原因:nvidia runtime 库路径
|
# 1. llama-server 必须在 GPU(推理用)→ /health 必须含 CUDA
|
||||||
# 找不到 / 缺 cuDNN)。此时必须报警。
|
# 2. bge-embed 可在 CPU 或 GPU(embedding 低频)→ 不强求 CUDA
|
||||||
|
# 3. GPU 显存 > 92% 报警
|
||||||
|
#
|
||||||
|
# 之前"bge 必须 CUDA"的判断是错的——bge 改 CPU 是为腾显存给 llama。
|
||||||
|
# 现在 bge 状态不报警(除非 /health 完全无响应)。
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ALERT=""
|
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)
|
BGE_HEALTH=$(curl -s -m 5 http://localhost:8000/health 2>/dev/null || true)
|
||||||
if [ -z "$BGE_HEALTH" ]; then
|
if [ -z "$BGE_HEALTH" ]; then
|
||||||
# /health 无响应 → 端口未监听 → 真异常 → 重试
|
# /health 无响应 → 端口未监听 → 真异常
|
||||||
ALERT="$ALERT\n❌ bge-embed 服务异常(/health 无响应)"
|
ALERT="$ALERT\n❌ bge-embed 服务异常(/health 无响应,:8000 未监听)"
|
||||||
systemctl --user restart bge-embed 2>/dev/null || true
|
systemctl --user restart bge-embed 2>/dev/null || true
|
||||||
sleep 5
|
sleep 5
|
||||||
BGE_HEALTH2=$(curl -s -m 5 http://localhost:8000/health 2>/dev/null || true)
|
BGE_HEALTH2=$(curl -s -m 5 http://localhost:8000/health 2>/dev/null || true)
|
||||||
|
|
@ -24,9 +43,6 @@ if [ -z "$BGE_HEALTH" ]; then
|
||||||
else
|
else
|
||||||
ALERT="$ALERT\n✅ bge-embed 已自动重启恢复"
|
ALERT="$ALERT\n✅ bge-embed 已自动重启恢复"
|
||||||
fi
|
fi
|
||||||
elif ! echo "$BGE_HEALTH" | grep -q "CUDAExecutionProvider"; then
|
|
||||||
# 端口在,但没用 CUDA — 异常,CUDA runtime 库路径/版本可能有问题
|
|
||||||
ALERT="$ALERT\n❌ bge-embed 起来了但未用 CUDAExecutionProvider(providers=$BGE_HEALTH)"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 2. GPU 状态
|
# 2. GPU 状态
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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 的"反向约束"机制管住我(沉默 = 失职的反面也适用:路由失败 = 失职)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🧪 实测验证
|
||||||
|
|
||||||
|
### 测试 1:research 路由
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ python3 ~/.hermes/scripts/kanban-route.py "测试路由:调研 Python 异步框架对比"
|
||||||
|
✅ 任务已创建: t_ba32ea18 (research)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 2:NPC 路由
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ python3 ~/.hermes/scripts/kanban-route.py "测试 NPC 路由固化:让 NPC 写个函数" --npc
|
||||||
|
✅ Issue 已创建: #1 in muchen-org/auto-npc
|
||||||
|
```
|
||||||
|
|
||||||
|
### 测试 3:default 路由
|
||||||
|
|
||||||
|
```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*
|
||||||
|
*状态:✅ 全部固化完成*
|
||||||
|
|
@ -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
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"last_report_path": "/home/muc/.hermes/profiles/prof-b/logs/curator/20260825-143653",
|
"last_report_path": "/home/muc/.hermes/logs/curator/20260901-145714",
|
||||||
"last_run_at": "2026-08-25T14:36:53.254154+00:00",
|
"last_run_at": "2026-09-01T14:57:14.689382+00:00",
|
||||||
"last_run_duration_seconds": 1.43867,
|
"last_run_duration_seconds": 5.117237,
|
||||||
"last_run_summary": "auto: 1 marked stale, 1 reactivated; llm: skipped (consolidation off)",
|
"last_run_summary": "auto: 2 marked stale, 1 reactivated; llm: skipped (consolidation off)",
|
||||||
"last_run_summary_shown_at": null,
|
"last_run_summary_shown_at": null,
|
||||||
"paused": false,
|
"paused": false,
|
||||||
"run_count": 16
|
"run_count": 17
|
||||||
}
|
}
|
||||||
|
|
@ -344,16 +344,16 @@
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
"created_at": "2026-07-23T09:27:16.552310+00:00",
|
"created_at": "2026-07-23T09:27:16.552310+00:00",
|
||||||
"created_by": "agent",
|
"created_by": "agent",
|
||||||
"last_patched_at": "2026-07-23T14:07:01.001333+00:00",
|
"last_patched_at": "2026-09-01T13:24:30.353399+00:00",
|
||||||
"last_reused_patch_generation": 0,
|
"last_reused_patch_generation": 2,
|
||||||
"last_used_at": "2026-09-01T12:24:50.584267+00:00",
|
"last_used_at": "2026-09-01T14:46:17.329481+00:00",
|
||||||
"last_viewed_at": "2026-09-01T12:24:50.579574+00:00",
|
"last_viewed_at": "2026-09-01T14:46:17.312828+00:00",
|
||||||
"patch_count": 2,
|
"patch_count": 4,
|
||||||
"patch_generation": 0,
|
"patch_generation": 2,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 6,
|
"use_count": 16,
|
||||||
"view_count": 6
|
"view_count": 16
|
||||||
},
|
},
|
||||||
"blocked-page-recovery": {
|
"blocked-page-recovery": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -540,16 +540,16 @@
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
"created_at": "2026-08-09T12:25:17.949527+00:00",
|
"created_at": "2026-08-09T12:25:17.949527+00:00",
|
||||||
"created_by": "agent",
|
"created_by": "agent",
|
||||||
"last_patched_at": "2026-08-15T17:13:55.335192+00:00",
|
"last_patched_at": "2026-09-01T13:35:53.465773+00:00",
|
||||||
"last_reused_patch_generation": 12,
|
"last_reused_patch_generation": 14,
|
||||||
"last_used_at": "2026-08-15T17:13:11.742416+00:00",
|
"last_used_at": "2026-09-01T13:32:55.589413+00:00",
|
||||||
"last_viewed_at": "2026-08-15T17:13:11.738829+00:00",
|
"last_viewed_at": "2026-09-01T13:32:55.580024+00:00",
|
||||||
"patch_count": 13,
|
"patch_count": 16,
|
||||||
"patch_generation": 13,
|
"patch_generation": 16,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 6,
|
"use_count": 11,
|
||||||
"view_count": 6
|
"view_count": 11
|
||||||
},
|
},
|
||||||
"code-intelligence": {
|
"code-intelligence": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -839,14 +839,29 @@
|
||||||
"created_by": "agent",
|
"created_by": "agent",
|
||||||
"last_patched_at": "2026-08-26T05:42:43.675272+00:00",
|
"last_patched_at": "2026-08-26T05:42:43.675272+00:00",
|
||||||
"last_reused_patch_generation": 2,
|
"last_reused_patch_generation": 2,
|
||||||
"last_used_at": "2026-09-01T12:24:33.964455+00:00",
|
"last_used_at": "2026-09-01T12:49:40.492187+00:00",
|
||||||
"last_viewed_at": "2026-09-01T12:24:33.959928+00:00",
|
"last_viewed_at": "2026-09-01T12:49:40.486311+00:00",
|
||||||
"patch_count": 98,
|
"patch_count": 98,
|
||||||
"patch_generation": 2,
|
"patch_generation": 2,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 87,
|
"use_count": 88,
|
||||||
"view_count": 87
|
"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": {
|
"devops/devops-umbrella": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -887,6 +902,21 @@
|
||||||
"use_count": 2,
|
"use_count": 2,
|
||||||
"view_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": {
|
"devops/self-hosted-tunneling": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
"created_at": "2026-06-19T19:10:21.517068+00:00",
|
"created_at": "2026-06-19T19:10:21.517068+00:00",
|
||||||
|
|
@ -1223,6 +1253,21 @@
|
||||||
"use_count": 18,
|
"use_count": 18,
|
||||||
"view_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": {
|
"github-auth": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
"created_at": "2026-06-30T11:24:39.499832+00:00",
|
"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",
|
"last_viewed_at": "2026-08-01T14:20:59.356526+00:00",
|
||||||
"patch_count": 4,
|
"patch_count": 4,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "stale",
|
||||||
"use_count": 2,
|
"use_count": 2,
|
||||||
"view_count": 2
|
"view_count": 2
|
||||||
},
|
},
|
||||||
|
|
@ -1526,14 +1571,14 @@
|
||||||
"created_by": null,
|
"created_by": null,
|
||||||
"last_patched_at": "2026-08-28T13:02:49.751591+00:00",
|
"last_patched_at": "2026-08-28T13:02:49.751591+00:00",
|
||||||
"last_reused_patch_generation": 2,
|
"last_reused_patch_generation": 2,
|
||||||
"last_used_at": "2026-09-01T12:14:15.767842+00:00",
|
"last_used_at": "2026-09-01T13:32:55.584589+00:00",
|
||||||
"last_viewed_at": "2026-09-01T12:14:15.759086+00:00",
|
"last_viewed_at": "2026-09-01T13:32:55.575007+00:00",
|
||||||
"patch_count": 125,
|
"patch_count": 125,
|
||||||
"patch_generation": 2,
|
"patch_generation": 2,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 146,
|
"use_count": 148,
|
||||||
"view_count": 145
|
"view_count": 147
|
||||||
},
|
},
|
||||||
"hermes-desktop-kanban": {
|
"hermes-desktop-kanban": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -1610,14 +1655,14 @@
|
||||||
"created_by": null,
|
"created_by": null,
|
||||||
"last_patched_at": "2026-08-12T05:13:32.138839+00:00",
|
"last_patched_at": "2026-08-12T05:13:32.138839+00:00",
|
||||||
"last_reused_patch_generation": 1,
|
"last_reused_patch_generation": 1,
|
||||||
"last_used_at": "2026-08-30T09:41:13.404893+00:00",
|
"last_used_at": "2026-09-01T13:17:29.163415+00:00",
|
||||||
"last_viewed_at": "2026-08-30T09:41:13.395293+00:00",
|
"last_viewed_at": "2026-09-01T13:17:29.139591+00:00",
|
||||||
"patch_count": 81,
|
"patch_count": 81,
|
||||||
"patch_generation": 1,
|
"patch_generation": 1,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 138,
|
"use_count": 139,
|
||||||
"view_count": 127
|
"view_count": 128
|
||||||
},
|
},
|
||||||
"hermes-venv-dependency-safety": {
|
"hermes-venv-dependency-safety": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -1759,14 +1804,44 @@
|
||||||
"created_by": null,
|
"created_by": null,
|
||||||
"last_patched_at": null,
|
"last_patched_at": null,
|
||||||
"last_reused_patch_generation": 0,
|
"last_reused_patch_generation": 0,
|
||||||
"last_used_at": "2026-09-01T12:20:29.962075+00:00",
|
"last_used_at": "2026-09-01T13:33:05.681821+00:00",
|
||||||
"last_viewed_at": "2026-09-01T12:20:29.928252+00:00",
|
"last_viewed_at": "2026-09-01T13:33:05.672818+00:00",
|
||||||
"patch_count": 0,
|
"patch_count": 0,
|
||||||
"patch_generation": 0,
|
"patch_generation": 0,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 1,
|
"use_count": 5,
|
||||||
"view_count": 1
|
"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": {
|
"karpathy-code-discipline": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -1908,7 +1983,7 @@
|
||||||
"patch_count": 36,
|
"patch_count": 36,
|
||||||
"patch_generation": 3,
|
"patch_generation": 3,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "stale",
|
"state": "active",
|
||||||
"use_count": 44,
|
"use_count": 44,
|
||||||
"view_count": 44
|
"view_count": 44
|
||||||
},
|
},
|
||||||
|
|
@ -2341,7 +2416,7 @@
|
||||||
"last_viewed_at": "2026-07-31T18:14:50.228349+00:00",
|
"last_viewed_at": "2026-07-31T18:14:50.228349+00:00",
|
||||||
"patch_count": 4,
|
"patch_count": 4,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "stale",
|
||||||
"use_count": 9,
|
"use_count": 9,
|
||||||
"view_count": 9
|
"view_count": 9
|
||||||
},
|
},
|
||||||
|
|
@ -2620,14 +2695,14 @@
|
||||||
"created_by": "agent",
|
"created_by": "agent",
|
||||||
"last_patched_at": "2026-08-29T12:20:33.818984+00:00",
|
"last_patched_at": "2026-08-29T12:20:33.818984+00:00",
|
||||||
"last_reused_patch_generation": 2,
|
"last_reused_patch_generation": 2,
|
||||||
"last_used_at": "2026-08-29T13:12:08.253798+00:00",
|
"last_used_at": "2026-09-01T12:59:01.978539+00:00",
|
||||||
"last_viewed_at": "2026-08-29T13:12:08.244932+00:00",
|
"last_viewed_at": "2026-09-01T12:59:01.974290+00:00",
|
||||||
"patch_count": 2,
|
"patch_count": 2,
|
||||||
"patch_generation": 2,
|
"patch_generation": 2,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 8,
|
"use_count": 9,
|
||||||
"view_count": 8
|
"view_count": 9
|
||||||
},
|
},
|
||||||
"provider-tiering": {
|
"provider-tiering": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
@ -2784,16 +2859,16 @@
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
"created_at": "2026-07-08T18:13:02.034240+00:00",
|
"created_at": "2026-07-08T18:13:02.034240+00:00",
|
||||||
"created_by": "agent",
|
"created_by": "agent",
|
||||||
"last_patched_at": "2026-08-25T09:13:48.358348+00:00",
|
"last_patched_at": "2026-09-01T13:48:27.218650+00:00",
|
||||||
"last_reused_patch_generation": 25,
|
"last_reused_patch_generation": 27,
|
||||||
"last_used_at": "2026-08-30T08:57:58.764192+00:00",
|
"last_used_at": "2026-09-01T14:46:17.334034+00:00",
|
||||||
"last_viewed_at": "2026-08-30T08:57:58.754342+00:00",
|
"last_viewed_at": "2026-09-01T14:46:17.325238+00:00",
|
||||||
"patch_count": 228,
|
"patch_count": 230,
|
||||||
"patch_generation": 25,
|
"patch_generation": 27,
|
||||||
"pinned": false,
|
"pinned": false,
|
||||||
"state": "active",
|
"state": "active",
|
||||||
"use_count": 180,
|
"use_count": 191,
|
||||||
"view_count": 180
|
"view_count": 191
|
||||||
},
|
},
|
||||||
"self-hosted-tunneling": {
|
"self-hosted-tunneling": {
|
||||||
"archived_at": null,
|
"archived_at": null,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
name: bge-embed-crash-loop-fix
|
name: bge-embed-crash-loop-fix
|
||||||
version: 2026-09-01
|
version: 2026-09-01-v2
|
||||||
description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺依赖)的诊断与修复
|
description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺依赖 / CPU-GPU 切换设计)的诊断与修复
|
||||||
---
|
---
|
||||||
|
|
||||||
# bge-embed Crash Loop 排查与修复
|
# bge-embed Crash Loop 排查与修复
|
||||||
|
|
@ -9,6 +9,30 @@ description: bge-embed.service 崩溃重启循环(端口未释放 / venv 缺
|
||||||
## 症状
|
## 症状
|
||||||
bge-embed.service 反复崩溃重启,重启计数飙到 85337 次,但看起来"在线"。
|
bge-embed.service 反复崩溃重启,重启计数飙到 85337 次,但看起来"在线"。
|
||||||
|
|
||||||
|
## ⚠️ 修复前必做:拉现状 4 步(不要先猜根因)
|
||||||
|
|
||||||
|
2026-09-01 教训:我没拉现状就下结论"CPU 是这台 4GB 笔记本正常态",错了。看门狗报"未用 CUDA"被当成"看门狗误报"处理,结果把正确的报警逻辑改坏。**任何 bge 修复前必须先拉真实状态**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 服务在不在?(不是 systemd status,systemd 可能撒谎)
|
||||||
|
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 失败) → 根因 C(venv 缺依赖)
|
||||||
|
- 进程在、/health 200、但 `providers: ["CPUExecutionProvider"]` → 根因 C2(装了 CPU 版 onnxruntime 或 CUDA 库路径不对)
|
||||||
|
- 进程在、/health 200、`providers: ["CUDAExecutionProvider", ...]`、显存 600MB → **健康,不用修**(看门狗误报是另一码事)
|
||||||
|
|
||||||
|
**陷阱**:不要把"看门狗报未用 CUDA"当成"看门狗误报"——它可能就是真相。先拉现状再下判断。
|
||||||
|
|
||||||
## 诊断命令
|
## 诊断命令
|
||||||
```bash
|
```bash
|
||||||
# 日志
|
# 日志
|
||||||
|
|
@ -154,5 +178,36 @@ curl -s http://localhost:8000/health # 确认服务在线
|
||||||
## 关键指标
|
## 关键指标
|
||||||
- 崩溃原因:`Address already in use`(端口未释放)
|
- 崩溃原因:`Address already in use`(端口未释放)
|
||||||
- 修复后 RestartSec:10 秒
|
- 修复后 RestartSec:10 秒
|
||||||
- 正常内存:~1.5GB(ONNX 模型加载后)
|
- 正常显存:~600MB(CUDA 推理),CPU fallback 只有 ~200MB
|
||||||
- PID 应保持不变(不反复重启)
|
- PID 应保持不变(不反复重启)
|
||||||
|
- **历史正常态(v1)**:`/health` 返回 `CUDAExecutionProvider`;返回 `CPUExecutionProvider` 是异常
|
||||||
|
- **当前正常态(v2,2026-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(~600MB),llama 跑 CPU(9 t/s)
|
||||||
|
- **v2 设计(9 月 1 日 22:00 起,4GB 显存 + 7B 共存)**:bge 跑 CPU(~200MB),llama 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 tmpfs,systemd unit 永远不要写 `/tmp/...` 路径。
|
||||||
|
> 见 `references/llama-server-tmp-path-trap.md`
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
# bge-embed 复用 ComfyUI CUDA 13 库(2026-09-01 实测)
|
# CUDA 13 库复用模式 — 4GB 显存笔记本(2026-09-01 实测)
|
||||||
|
|
||||||
> 问题:bge-embed 装了 `onnxruntime-gpu` 但 `/health` 返回 `CPUExecutionProvider`
|
> 场景:bge-embed / llama.cpp / 其他需要 CUDA 的本地服务,不想重装 CUDA toolkit。
|
||||||
> 根因:CUDA 13 runtime 库不在 LD_LIBRARY_PATH,onnxruntime 加载 CUDA EP 失败
|
|
||||||
|
|
||||||
## 本机配置(不装 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/
|
/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/
|
/home/muc/ComfyUI/venv/lib/python3.11/site-packages/nvidia/cudnn/lib/
|
||||||
├── libcudnn.so.9
|
├── libcudnn.so.9
|
||||||
├── libcudnn_adv.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` 加这两条:
|
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
|
- `nvidia-cu13`、`nvidia-cudnn-cu13` 是 pip 包,解压到 venv 的 site-packages
|
||||||
- CUDA runtime .so 文件在 venv 隔离路径里,系统 `/usr/local/cuda` 不存在也能用
|
- CUDA runtime .so 文件在 venv 隔离路径里,系统 `/usr/local/cuda` 不存在也能用
|
||||||
- 只要把 venv 的 nvidia 路径加入 LD_LIBRARY_PATH,动态链接器就能找到
|
- 只要把 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
|
```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 \
|
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())"
|
python3 -c "import onnxruntime as ort; print(ort.get_available_providers())"
|
||||||
# 应输出:['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
|
# 应输出:['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||||
|
|
||||||
# 2. 检查实际加载模型时的 provider
|
# 2. 实际加载模型
|
||||||
LD_LIBRARY_PATH=... python3 -c "
|
LD_LIBRARY_PATH=... python3 -c "
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
sess = ort.InferenceSession('model.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
sess = ort.InferenceSession('model.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
||||||
print(sess.get_providers())
|
print('实际 providers:', sess.get_providers())
|
||||||
"
|
"
|
||||||
# 应输出:['CUDAExecutionProvider', 'CPUExecutionProvider']
|
# 应输出:['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||||
|
|
||||||
# 3. 检查显存占用(GPU 推理 ~600MB,CPU fallback ~200MB)
|
# 3. 看显存(GPU 推理 ~600MB for bge,纯 CPU ~200MB)
|
||||||
nvidia-smi --query-gpu=memory.used --format=csv
|
nvidia-smi --query-gpu=memory.used --format=csv,noheader
|
||||||
```
|
```
|
||||||
|
|
||||||
## 通用原则
|
## 通用公式
|
||||||
|
|
||||||
- **不用重装 CUDA toolkit**:复用已有 venv 的 nvidia 包更干净
|
**任何需要 CUDA 的 Python 服务**:
|
||||||
- **版本对齐**:onnxruntime-gpu X.Y 需匹配 CUDA X.x + cuDNN 9.x(见 https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html)
|
```bash
|
||||||
- **快速判断**:`curl /health | grep CUDAExecutionProvider` 比看日志更快
|
# 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
|
# 3. 验证 /health + nvidia-smi
|
||||||
- ComfyUI venv: `/home/muc/ComfyUI/venv/`
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
*2026-09-01 | 本地点火修复* — 详见 `bge-embed-crash-loop-fix` 根因 C2
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,88 @@ curl -s -X POST "https://api.cnb.cool/<org>/<repo>/-/issues/1/comments" \
|
||||||
- 每 10 分钟跑一次,**monitor 模式**:NPC 有进展(评论/PR 变化)才触发汇报,静默零消耗
|
- 每 10 分钟跑一次,**monitor 模式**:NPC 有进展(评论/PR 变化)才触发汇报,静默零消耗
|
||||||
- 手动立即检查:`python3 ~/.hermes/scripts/cnb_npc_monitor.py`
|
- 手动立即检查:`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
|
||||||
|
```
|
||||||
|
|
||||||
|
**看板优势**:
|
||||||
|
- 任务有 ID(t_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 里发现)
|
## API 端点速查(swagger 里发现)
|
||||||
|
|
||||||
| 操作 | 端点 |
|
| 操作 | 端点 |
|
||||||
|
|
|
||||||
|
|
@ -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 创建和评论触发时都带上**。
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
---
|
---
|
||||||
name: self-healing-infrastructure
|
name: self-healing-infrastructure
|
||||||
description: "自愈基础设施 — 系统监控、配置版本控制、自动回滚、自进化管线、技能管理、自我优化、学习闭环。完整自治体系。牧尘专用。debug铁律:函数存在≠真的在工作,必须验证文件输出。"
|
description: "自愈基础设施 — 系统监控、配置版本控制、自动回滚、自进化管线、技能管理、自我优化、学习闭环。完整自治体系。牧尘专用。debug铁律:函数存在≠真的在工作,必须验证文件输出。"
|
||||||
version: 1.26.0
|
version: 1.27.0
|
||||||
date: 2026-08-30
|
date: 2026-09-01-v2
|
||||||
author: 小唯 A06
|
author: 小唯 A06
|
||||||
tags: [self-healing, monitoring, auto-rollback, evolution, watchdog, config-protection, daemon, backup, recovery]
|
tags: [self-healing, monitoring, auto-rollback, evolution, watchdog, config-protection, daemon, backup, recovery]
|
||||||
category: devops
|
category: devops
|
||||||
|
|
@ -192,7 +192,7 @@ trigger: 系统部署、开机自启、配置更改、故障恢复场景、备
|
||||||
**daemon.py 外部命令输出陷阱(2026-07-25)**:
|
**daemon.py 外部命令输出陷阱(2026-07-25)**:
|
||||||
`int(外部命令输出)` 必须包 `try/except ValueError` — nvidia-smi / iostat / df 等在驱动/权限/环境异常时输出报错字符串而非数字。详见 `references/feishu-delivery-queue-dns-recovery-20260725.md`。
|
`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`。**改任何健康检查前先确认数据源真实更新频率。**
|
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`(不告警)。
|
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 应静默,只在真异常时告警。**
|
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,立即失败)
|
- `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 影响且能拿到真实返回码)
|
- 裸 `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`
|
- 详见 `references/watchdog-freshness-cadence-20260812.md`
|
||||||
- **QUIET=1 静默模式(no_agent cron 的"常态不打扰"实现,2026-08-12)**:外部依赖不可达且是常态时(如家庭服务器不在局域网),cron 每次报 error 会刷屏。实现:`log()` 函数按 `QUIET=1` 只写文件不写 stdout;wrapper 里 `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` 只写文件不写 stdout;wrapper 里 `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-level,v2 更新 2026-09-01)**:看门狗写死的"正常态"必须**有真实运行证据**,不能拍脑袋。**但**"GPU 是正常态"也不是普适铁律——4GB 显存笔记本上同时跑 bge + llama 7B 时,**bge 主动改 CPU 是合理设计选择**(腾显存给 llama),不是退化。铁律:拉现状 + 了解资源约束 + 验证"为什么这么设计"再下判断。"应该是 X" = 反向信号 = 现在就 curl/grep 验证。**正确说法:v1(9 月 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 全 GPU(2700MB),推理速度从 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`
|
- **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 直接 import(201 创建,返回完整 repo JSON)
|
- **2026-07-20 新增 GitHub API import 方式**:Gitea 用户 push 新建仓库会 403,用 `POST /repos/migrate` 从 GitHub URL 直接 import(201 创建,返回完整 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`。
|
- `memory-system-self-upgrade.py` — **每日4点自升**:L7 llm_context.json v2 9字段验证(新增) + 织忆tombstone增长检测+recall_hit健康度 + Soulful清理30天前cares+心迹去重+distilled_rules补充 + TencentDB capture写入验证 + 数据量报告。异常飞书。cron `691709a8b4cf`。
|
||||||
|
|
|
||||||
|
|
@ -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 Q3(3.6GB)+ bge(600MB)**。
|
||||||
|
|
||||||
|
## 性能取舍矩阵
|
||||||
|
|
||||||
|
| 配置 | 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 设计切换验证*
|
||||||
|
|
@ -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 模式: ~4s(25 t/s)
|
||||||
|
# 混合模式: ~8-12s(10-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,是你的方式不对"后实测验证*
|
||||||
|
|
@ -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 ← 不够!
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:部分层在 GPU,KV 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/s,prompt 快生成慢 | 当前状态 |
|
||||||
|
|
||||||
|
### 诊断方法
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 看 GPU 占用(判断是否全模型在 GPU)
|
||||||
|
nvidia-smi --query-gpu=memory.used --format=csv,noheader
|
||||||
|
# 正常(7B 全 GPU):2700-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 | 本地点火修复*
|
||||||
|
|
@ -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*
|
||||||
Loading…
Reference in New Issue