2026-05-08 00:00:00

最近给 Mac Studio 配了一套监控方案,通过 macmon-prometheus-exporter 采集硬件指标,接入 Prometheus + Grafana。整个过程比想象中简单,记录一下细节。


参考macmon - Apple Silicon Mac 的性能监控利器
macmon-exporter 通过 Homebrew 一键安装:
brew install macmon-prometheus-exporter
安装后会自动注册为 launchd 服务,监听 9101 端口。验证一下:
curl http://localhost:9101/metrics | head -20
macmon-exporter 暴露的指标都在 mac_ 命名空间下,都是 Gauge 类型(瞬时值):
| 指标 | 含义 | 示例值 |
|---|---|---|
mac_cpu_usage_percent |
CPU 总使用率(%) | 27.69 |
mac_gpu_usage_percent |
GPU 使用率(%) | 100.0 |
mac_cpu_power_watts |
CPU 功耗(瓦特) | 4.88 |
mac_gpu_power_watts |
GPU 功耗(瓦特) | 50.86 |
mac_cpu_temperature_celsius |
CPU 温度(摄氏度) | 52.69 |
mac_gpu_temperature_celsius |
GPU 温度(摄氏度) | 48.97 |
mac_memory_usage_percent |
内存使用率(%) | 62.00 |
mac_memory_total_bytes |
内存总量(字节) | 103079215104 |
注意:macmon-exporter 提供的是瞬时百分比值,不是累积计数器。这意味着在 Grafana 中不需要使用
irate()或rate()。
prometheus.yml
- job_name: 'node-studio'
static_configs:
- targets: ['studio.local:9101']
labels:
exported_instance: "studio"
node: "studio"
Linux 节点通常用 irate(node_cpu_seconds_total{mode="idle"}) 计算 CPU 使用率,但 Mac Studio 的 macmon-exporter 直接提供百分比值:
# CPU 使用率(除以100因为Grafana自带百分号)
avg(mac_cpu_usage_percent{node="studio"}) by (node) / 100
# GPU 使用率
avg(mac_gpu_usage_percent{node="studio"}) by (node) / 100
Linux 节点用 count(node_cpu_seconds_total{mode="system"}) 统计核心数,Mac Studio 需要分别查询 P-Core 和 E-Core:
# Mac Studio: 8P + 4E = 12 核心
sum(mac_pcpu_cores{node="studio"}) + sum(mac_ecpu_cores{node="studio"})
同样,macmon-exporter 直接提供百分比:
# 内存使用率(除以100)
avg(mac_memory_usage_percent{node="studio"}) by (node) / 100
# 内存总量(注意单位)
mac_memory_total_bytes{node="studio"} / 1024^3
踩坑:Grafana 默认使用十进制单位(1 GB = 10^9 bytes),所以
mac_memory_total_bytes直接除以1024^3后显示为 96 GiB(二进制),而不是 103 GB(十进制)。在 Grafana 面板中将 Unit 设为gibibytes (GiB)即可正确显示。
# CPU 温度
mac_cpu_temperature_celsius{node="studio"}
# GPU 温度
mac_gpu_temperature_celsius{node="studio"}
Apple Silicon 的功耗数据很有参考价值:
# CPU 功耗(瓦特)
mac_cpu_power_watts{node="studio"}
# GPU 功耗(瓦特)
mac_gpu_power_watts{node="studio"}
# 系统总功耗
mac_system_power_watts{node="studio"}
# 过去1小时的平均系统功耗
avg_over_time(mac_system_power_watts{node="studio"}[1h])
# 过去24小时的平均系统功耗
avg_over_time(mac_system_power_watts{node="studio"}[24h])
# 过去7天的平均系统功耗
avg_over_time(mac_system_power_watts{node="studio"}[7d])
瞬时值 vs 计数器:macmon-exporter 的百分比指标是 Gauge,不需要 irate()。只有累积计数器才需要 rate/irate。
单位转换:Grafana 的 Unit 设置会影响显示值,注意区分 GB(十进制)和 GiB(二进制)。
温度监控:macmon-exporter 只暴露 CPU/GPU 温度,没有硬盘 SMART 信息。如果需要磁盘温度,需要额外安装 smartmontools。
2026-05-08 00:00:00

最近给 QwenPaw 配置 LM Studio 时,发现一个现象:LLM 请求的 KV Cache 命中率始终为 0。
排查了一圈配置和代理链路,最终发现这不是 bug,而是 Apple Silicon (MLX) 与 NVIDIA CUDA 在 KV Cache 实现上的根本性差异。记录一下排查过程和实验数据。
我们的请求链路如下:
QwenPaw (Linux) → Docker(cli-proxyapi:8317) → LM Studio (Mac M2 Max, 100.100.100.11:1234)
在 LM Studio 的 Web UI 中,我已经开启了 KV缓存量化 (Experimental),上下文长度设为 262144。但 QwenPaw 的 token 用量日志显示,每次请求都是全新的 prompt,缓存从未命中。
首先怀疑是 cli-proxyapi 在转发时注入了动态 header(如 X-Request-Id、时间戳等),导致每次请求的 HTTP 头不一致。
curl -sv ... http://cli-proxyapi.sh4.local:8317/v1/chat/completions 2>&1 | grep -E "^[<>]"
结果:两次请求的 header 完全一致,代理层干净。
如果 LM Studio 背后有多台机器轮询,缓存自然无法复用。我是单实例运行,无负载均衡。
我构造了两组对比实验:
实验 A:完全相同的 prompt 重复发送
{"model":"qwen3.6-35b-a3b@8bit","messages":[{"role":"system","content":"You are a test bot."},{"role":"user","content":"What is 1+1?"},{"role":"assistant","content":"2"},{"role":"user","content":"And 2+2?"}],"max_tokens":5,"temperature":0}
实验 B:相同前缀(100K chars) + 不同后缀(模拟 Agent 多轮对话)
PREFIX=$(python3 -c "print('A'*100000)") # 约 25K tokens
# Req1: system="$PREFIX", user="Say ONE word: test"
# Req2: system="$PREFIX", user="Say ONE word: test" (完全相同)
实验数据说明了原因:
| 场景 | CUDA (vLLM/TGI) | Apple Silicon (MLX/LM Studio) |
|---|---|---|
| 完全相同的 prompt | ✅ 命中,复用 KV Cache | ✅ 命中,复用 KV Cache |
| 相同前缀 + 不同后缀 | ✅ 自动前缀缓存 (Prefix Caching) | ❌ 不支持,必须逐字节完全匹配 |
QwenPaw 的 Agent 场景每轮发送的 prompt 结构如下:
[固定 System Prompt (~15K tokens)] + [变化的对话历史] + [新的 User Message]
↑ 前缀固定 ↑ 每轮变化
在 CUDA 推理框架中,vLLM/TGI 会自动提取相同的前缀进行 KV Cache 复用,Agent 场景的缓存命中率通常能达到 30%~50%。
但在 Apple Silicon 上,LM Studio 底层使用 MLX 框架。MLX 的 KV Cache 实现较为朴素,仅支持精确匹配(Exact Match),不支持前缀缓存 (Prefix Caching)。Apple Silicon 的 KV Cache 优化目前仍处于实验阶段,前缀复用功能尚未稳定。
date +%s%N 记录毫秒级耗时,配合完全相同的 prompt vs 前缀相同后缀不同的对比,能最快定位缓存生效边界。2026-05-03 00:00:00

最近在折腾本地 AI 代理服务,把 CLIProxyAPI 和 CPA Usage Keeper 部署到了 Docker 里,记录一下过程。
CLIProxyAPI 是一个开源的 AI API 代理服务,主要用来:
对于我这种无法直接访问 OpenAI/Gemini 官方 API 的人来说,它是个不错的解决方案。可以把请求转发到 OpenRouter,由 OpenRouter 帮我调用意愿的模型。
CPA Usage Keeper(简称 CPA)是 CLIProxyAPI 的配套管理工具,主要功能:
简单说就是给 CLIProxyAPI 做一个”后台”,方便查看花了多少钱、用了多少 Token。
创建工作目录:
mkdir -p /root/docker/cliproxyapi
cd /root/docker/cliproxyapi
配置文件 config.yaml:
host: "0.0.0.0"
port: 8317
remote-management:
allow-remote: true
secret-key: "my-simple-key"
api-keys:
- "cliproxy-default-key"
openai-compatibility:
- name: "openrouter"
prefix: "openrouter"
base-url: "https://openrouter.ai/api/v1"
headers:
Content-Type: "application/json"
api-key-entries:
- api-key: "你的 OpenRouter API Key"
models:
- name: "minimax/minimax-m2.5:free"
alias: "minimax-m2.5-free"
记得开启 usage-statistics-enabled,否则不会记录使用量:
host: "0.0.0.0"
port: 8317
remote-management:
allow-remote: true
secret-key: "my-key"
api-keys:
- "cliproxy-key"
openai-compatibility:
- name: "openrouter"
prefix: "openrouter"
base-url: "https://openrouter.ai/api/v1"
headers:
Content-Type: "application/json"
api-key-entries:
- api-key: "你的 OpenRouter API Key"
models:
- name: "minimax/minimax-m2.5:free"
alias: "minimax-m2.5-free"
- name: DeepSeek
base-url: https://api.deepseek.com
models:
- name: deepseek-v4-pro
- name: deepseek-v4-flash
headers:
Authorization: Bearer 你的DeepSeek-API-Key
Content-Type: application/json
prefix: ds
- name: lmstudio
prefix: studio
base-url: http://本地IP:1234/v1
models:
- name: qwen3.6-35b-a3b@8bit
usage-statistics-enabled: true
version: '3'
services:
cli-proxy-api:
image: eceasy/cli-proxy-api:latest
container_name: cli-proxyapi
network_mode: bridge
volumes:
- ./config.yaml:/CLIProxyAPI/config.yaml
- ./auths:/root/.cli-proxy-api
- ./logs:/CLIProxyAPI/logs
restart: unless-stopped
cpa-usage-keeper:
image: ghcr.io/willxup/cpa-usage-keeper:latest
container_name: cli-cpa
restart: unless-stopped
network_mode: bridge
environment:
- TZ=Asia/Shanghai
- CPA_BASE_URL=http://cliproxyapi.local:8317
- REDIS_QUEUE_ADDR=cliproxyapi.local:8317
- CPA_MANAGEMENT_KEY=my-key
- AUTH_ENABLED=true
- LOGIN_PASSWORD=password
volumes:
- ./keeper:/data
docker-compose up -d
检查容器状态:
docker ps | grep cli-
CLIProxyAPI 默认监听在 8317,CPA 默认监听在 8080。
配置好模型后,就可以像调用普通 OpenAI API 一样调用 CLIProxyAPI:
curl http://cliproxyapi:8317/v1/chat/completions \
-H "Authorization: Bearer cliproxy-key" \
-H "Content-Type: application/json" \
-d '{
"model": "minimax-m2.5-free",
"messages": [
{"role": "user", "content": "Hello!"}
]
}'
参数说明:
| 参数 | 值 |
|---|---|
| 地址 | http://cliproxyapi:8317 |
| API Key |
cliproxy-key (config.yaml 中配置的 api-keys) |
| Model |
minimax-m2.5-free (config.yaml 中定义的 alias) |
如果需要添加更多模型,只需要在 config.yaml 的 openai-compatibility.models 中添加即可。
其他模型示例:
curl http://cliproxyapi:8317/v1/chat/completions \
-H "Authorization: Bearer cliproxy-key" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.6-35b-a3b@8bit",
"messages": [
{"role": "user", "content": "你好"}
]
}'
CLIProxyAPI 的 secret-key 支持明文配置,启动时会自动 bcrypt 哈希。所以直接写明文就行,别像我一开始那样直接贴个 bcrypt 哈希上去,结果两边密钥对不上。
试了下把 secret-key 设为空想免密,结果管理 API 直接返回 404 不可用了。CPA 还需要管理 API 来同步数据,所以没法完全免密。好在明文密钥两边会自动哈希匹配,保持一致很简单。
一开始 CPA 怎么都拉不到数据,后来发现 CLIProxyAPI 默认关闭了使用量统计。需要在 config.yaml 里加上:
usage-statistics-enabled: true
CPA 默认不带密码,如果暴露在公网很危险。启用登录保护:
environment:
- AUTH_ENABLED=true
- LOGIN_PASSWORD=password
CLIProxyAPI API: http://CLIProxyAPI:8317
CLIProxyAPI Dashboard: http://CLIProxyAPI:8317/v0/management

CPA 管理界面: http://CPA:8080

这两个工具配合起来挺香的,CLIProxyAPI 做代理转发,CPA 做使用量统计和密钥管理。内部网络环境下,通过 OpenRouter 访问各种模型还是很方便的。
2026-04-30 00:00:00

跑本地 LLM 模型的时候,一直想看 GPU 和 ANE 的实时功耗和温度。macOS 自带的 Activity Monitor 只有 CPU 使用率,看不到功耗数据。之前有 powermetrics 可以看,但要 sudo 权限,输出也是纯文本。后来发现了 macmon,Rust 写的 TUI 工具,不需要 sudo 就能看到芯片级的实时数据,果断装上。
通过 Homebrew 安装:
/opt/homebrew/bin/brew install macmon
⚠️ 仅支持 Apple Silicon(M1-M5),x86 的 Homebrew 会报架构不兼容。
macmon 通过 macOS 私有 API 采集数据,和 powermetrics 用同一个底层接口,但不需要 root 权限。主要功能包括:
c 切换macmon

运行后界面分几个区域:
键盘操作:
| 快捷键 | 功能 |
|---|---|
q |
退出 |
v |
切换 Sparkline / Gauge 视图 |
c |
切换配色主题 |
macmon -i 500
默认 1000ms 刷新,可以改成 500ms 更频繁。
macmon pipe -s 10
会输出 10 条 JSON 格式的指标数据,适合管道到 jq 或者其他脚本处理。
macmon -i 500 pipe -s 10 | jq
macmon 还支持把指标暴露为 HTTP 服务,方便接入 Prometheus / Grafana:
macmon serve # 默认 9090 端口
macmon serve -p 8080 # 自定义端口
macmon serve -i 500 # 采样间隔 500ms
两个端点:
| 端点 | 格式 | 说明 |
|---|---|---|
GET /json |
JSON | 当前指标快照 |
GET /metrics |
Prometheus | Prometheus 格式指标 |
接入 Prometheus 的配置示例:
scrape_configs:
- job_name: macmon
static_configs:
- targets: ["localhost:9090"]
macmon 是目前 Apple Silicon Mac 上最方便的终端监控工具,Rust 实现,单二进制文件,无需 sudo 就能看到芯片级功耗和温度。对于跑本地模型、监控系统负载来说非常实用。
2026-04-27 00:00:00

最近在使用 opencode 时遇到了一个奇怪的报错:
$ opencode
TypeError: fn3 is not a function. (In 'fn3(input)', 'fn3' is an instance of Object) at <anonymous> (src/plugin/index.ts:87:28)
但 opencode --help 和 opencode debug 却能正常运行。排查下来发现是版本过旧 + Homebrew upgrade 失效导致的问题,后续又牵连出一系列插件配置问题。记录一下完整的排查过程。
$ node --version
v18.20.8
$ npm --version
10.8.2
$ which opencode
/opt/homebrew/bin/opencode
$ opencode --version
1.1.50
怀疑是版本问题,尝试 brew upgrade opencode,但升级后版本仍然是 1.1.50。进一步查看:
$ brew info opencode
==> opencode: stable 1.4.10 (bottled)
AI coding agent, built for the terminal
https://opencode.ai
Installed:
/opt/homebrew/Cellar/opencode/1.1.50 (4 files, 101.5MB)
Tap 源已经有 1.4.10,但本地还停留在 1.1.50——brew upgrade 没有生效。
我最初安装 opencode 时,它还未被收录进 Homebrew 官方核心库(homebrew-core),而是通过第三方 tap anomalyco/tap 安装的。后来 opencode 被官方收录,但本地环境依然优先读取旧的第三方 tap,导致版本停滞在旧版本。
可以通过 brew info 的 “From” 行来确认来源:
# 旧的第三方 tap 来源
From: https://github.com/anomalyco/homebrew-tap/blob/HEAD/opencode.rb
# 新的官方来源
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/o/opencode.rb
brew uninstall opencode
这一步是关键,将 anomalyco/tap 从本地库中移除:
brew untap anomalyco/tap
brew update
brew install opencode
这一次系统会从 homebrew-core 拉取最新版本:
🍺 /opt/homebrew/Cellar/opencode/1.4.10: 12 files, 101.3MB
$ opencode --version
1.4.10
$ opencode --help
# 正常显示所有命令
$ brew postinstall opencode
# 确保 post-install 步骤执行完成
重新安装后,CLI 模式正常工作,TypeError 报错消失。
升级版本后,CLI 模式已经正常,但使用 Web 界面时又遇到新错误:
unknown certificate verification error
实际排查后发现,真正的原因是 API 验证错误:
{
"error": {
"code": 34,
"message": "request validation errors",
"errors": [{
"type": "Extra inputs are not permitted",
"loc": ["body", "tools[0]", "eager_input_streaming"],
"msg": "Extra inputs are not permitted",
"input": true
}]
}
}
opencode 的配置文件不在 ~/.opencode/,而是在:
$ ls ~/.config/opencode/
opencode.json oh-my-opencode.json.bak
查看 opencode.json 后发现:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["oh-my-openagent@latest"]
}
这里配置的 oh-my-openagent 插件向 Minimax API 发送了不支持的 eager_input_streaming 参数,导致验证失败。
将插件列表清空:
{
"$schema": "https://opencode.ai/config.json",
"plugin": []
}
禁用插件后,opencode run "你好" 可以正常收到 AI 回复。
opencode 本身只是一个框架,真正的能力来自插件。oh-my-opencode(后改名为 oh-my-openagent)是 opencode 的插件集合,提供多种专业 agent。
bunx oh-my-opencode install --no-tui --skip-auth
--no-tui 参数可以跳过图形界面交互,适合非交互式环境。
安装后可能会遇到模型验证问题:
Agent atlas's configured model opencode/glm-4.7-free is not valid
解决方法是更换为有效的模型,例如 opencode/big-pickle 或 opencode/gpt-5-nano。
2026 年 3 月发生了一个重要变化:
| 项目 | 旧名称 | 新名称 |
|---|---|---|
| npm 包 | oh-my-opencode | oh-my-openagent(v3.11.0, 2026-03-07) |
| 配置文件 | oh-my-opencode.json | 保留不变 |
| Schema 文件 | oh-my-opencode.schema.json | 保留不变 |
配置文件名称有意保留旧名以保持向后兼容:
# 两种配置名都能工作
~/.config/opencode/oh-my-opencode.json # 传统(推荐)
~/.config/opencode/oh-my-openagent.json # 新式
oh-my-opencode 中包含一套完整的 agent 协作体系:
| Agent | 角色 | 说明 |
|---|---|---|
| Sisyphus | 执行者 | 负责任务分解、代码实现 |
| Prometheus | 规划代理 | 通过 /start-work 激活,负责生成执行计划 |
| Metis | 预规划咨询 | 在规划阶段识别歧义和隐藏意图 |
| Momus | 计划评审 | 对执行计划进行清晰度和完整性审查 |
之前也有人问 opencode 和 openclaw 的区别:
| 特性 | opencode | openclaw |
|---|---|---|
| 类型 | AI 编程助手 | AI 控制器 |
| 核心 | 终端 IDE | 远程控制 |
| 通道 | 本地为主 | WhatsApp/Telegram |
| 定位 | 编码工作流 | 远程任务管理 |
两者定位不同,没有必要互相替换。
| 问题 | 解决方法 |
|---|---|
TypeError: fn3 is not a function |
卸载旧 tap 源,从 homebrew-core 重新安装最新版 |
eager_input_streaming 验证错误 |
清空 plugin 配置或更换兼容插件 |
| 配置目录找不到 | 记住在 ~/.config/opencode/ 而非 ~/.opencode/
|
几点经验教训:
brew upgrade 不一定可靠:如果升级后版本没变,可能需要先 untap 再重装unknown certificate verification error 是误报,真正的错误在 API 响应体里2026-04-22 00:00:00

把 QwenPaw(原 CoPaw)装好了,记录一下安装和配置过程。
QwenPaw 是 AgentScope 团队开源的个人 AI 助手,支持钉钉、飞书、微信、Discord 等多渠道,还能本地运行、扩展 Skills。GitHub 星标 15K+。
官方推荐三种安装方式:
pip install qwenpaw — 需要 Python 3.10+curl -fsSL https://qwenpaw.agentscope.io/install.sh | bash — 自动下载 uv我的系统自带 Python 是 3.9.6,pip 版本也老(21.2.4),而 qwenpaw 要求 Python 3.10-3.14。我平时用 conda 管理 Python 环境,所以直接走 conda 路线。
# 1. 创建 Python 3.11 虚拟环境
conda create -n qwenpaw python=3.11 -y
# 2. 激活环境
conda activate qwenpaw
# 3. 安装 qwenpaw
pip install qwenpaw
这样就搞定了,不需要额外引入 uv。环境激活后 python 和 pip 都指向 conda 环境内的版本,不会产生污染。
qwenpaw 内置支持 OpenRouter provider,配置很简单。编辑 ~/.copaw/config.json:
{
"$schema": "https://qwenpaw.agentscope.io/schema.json",
"model": {
"models": {
"minimax-free": {
"provider": "openrouter",
"model": "minimax/minimax-m2.5:free",
"base_url": "https://openrouter.ai/api/v1",
"api_key": "你的OPENROUTER_KEY",
"enabled": true
}
},
"default_model": "minimax-free"
}
}
注意:OpenRouter 的 MiniMax 模型 ID 格式是
minimax/minimax-m2.5:free,用冒号而不是连字符。
# 激活 conda 环境后,qwenpaw 命令可直接使用
conda activate qwenpaw
# 初始化(需要交互式终端)
qwenpaw init
# 或者启动 Web UI
qwenpaw app
启动后打开浏览器访问 http://127.0.0.1:8088/ 进行配置和聊天。

验证安装:
qwenpaw --version
# QwenPaw, version 1.1.3
安装完成只是第一步,真正的关键在于记忆文件配置。
QwenPaw 的工作区位于 ~/.copaw/workspaces/default/,包含以下文件:
| 文件 | 作用 | 优先级 |
|---|---|---|
PROFILE.md |
Agent 身份 + 用户资料 | 🔴 必须配置 |
SOUL.md |
Agent 灵魂宣言(行为准则) | 🔴 必须配置 |
MEMORY.md |
长期记忆(工具设置/经验教训) | 🟡 建议配置 |
AGENTS.md |
Agent 行为准则模板 | 🟢 可选定制 |
HEARTBEAT.md |
定时任务清单 | 🟢 可选配置 |

PROFILE.md 定义身份和偏好,编辑 SOUL.md 定义行为边界。memory/YYYY-MM-DD.md,定期提炼到 MEMORY.md。关键:QwenPaw 不会每次重启都重新初始化。记忆通过文件持久化,会话中断后自动恢复上下文。
qwenpaw 的依赖比较多(约 244 个包),包括:
首次安装需要下载不少内容,耐心等待即可。conda 环境下这些依赖会被隔离在环境目录中,不会影响系统。
| 工具 | 语言 | 特点 |
|---|---|---|
| QwenPaw | Python | 多渠道、本地优先、Skills 扩展 |
| OpenCode | TypeScript | IDE 集成、MCP 生态 |
| Claude Code | TypeScript | Anthropic 官方 |
QwenPaw 和 OpenCode 可以通过 ACP(Agent Communication Protocol)互联,默认配置中已开启:
"acp": {
"agents": {
"opencode": { "enabled": true },
"qwen_code": { "enabled": true },
"claude_code": { "enabled": true }
}
}