本地模型做 agentic coding 到底行不行:从 HN 917 分讨论到 M2 64GB 上的 Pi + LM Studio 全流程记录
最近 HN 上 Vickiboykis 那篇《Running local models is good now》拿下了 917 分、391 条评论,核心观点是:在 M2 Mac / 64 GB RAM 上跑本地模型做 agentic coding,现在已经达到 frontier 模型大约 75% 的准确率/速度。这条贴被讨论最多的不是"模型又变强了",而是"具体怎么搭起来"——很多人卡在 inference engine、agent harness、模型 artifact 三层之间的衔接。我顺着那条贴的 Docker Compose / Pi harness 配置在自己机器上完整跑了一遍,把能踩的坑和目前做不到的事都列下来。
一、为什么 75% 这个数字值得拿出来看
Vicki 自己给的口径是"vibe metric"——"我是不是还得拿 API 模型 double-check 一遍"。对大部分 dev 来说这是最朴素的判断:本地模型跑出来的代码要立刻能用,不能用就马上切换。前几年这个指标几乎是 0%(Mistral 7B 写出来的 Python 大概率有 type hint 错误,连 lint 都过不去),GPT-OSS 发布是个分水岭,到 Gemma-4 系列(12B QAT / 26B A4B)才稳定到 agentic loop 能反复跑的程度。
我在自己的 M2 / 64 GB 上复现的体感是:写单元测试、补 type hints、refactor 一个 notebook 到 5-6 个模块的小活儿,Gemma-4 12B QAT 跑出来的结果可以直接用,几乎不用复查。架构级的设计决策、跨文件重构、复杂的 bug 定位,还是需要切回 Claude/GPT。这一点和 Vicki 那个 75% 的判断基本吻合。
二、我具体搭了一套什么
完整复用了 Vickiboykis 在 6/15 那篇 blog 里给的配置,三层结构:
- Inference engine:LM Studio(v0.3.16 起 bundled 模型不强制 GGUF split,方便直接拖拽下载)
- Agent harness:Pi(0.74.0,Docker 镜像
pi-agent:0.74.0) - Model artifact:
google/gemma-4-12b-qat(约 8.2 GB 磁盘,QAT 量化精度损失比 INT4 小很多)
LM Studio 启动后默认监听 http://localhost:1234/v1,这是 OpenAI-compatible 的 completions endpoint。Pi 跑在 Docker 里,要从容器内访问宿主机的 LM Studio,关键就是 host.docker.internal:host-gateway 这条 extra_hosts。
1. Pi 的 models.json
放在宿主机的 ~/.pi/agent/models.json,容器里通过 volume 挂到 /config/models.json:
{"lmstudio": {"baseUrl": "http://host.docker.internal:1234/v1","api": "openai-completions","apiKey": "not-needed","models": [{"id": "google/gemma-4-12b-qat","input": ["text", "image"]}]}
}
注意 id 字段要和 LM Studio 里模型列表显示的名字完全一致(带 google/ 这个 org 前缀)。如果只写 gemma-4-12b-qat,Pi 会报 model not found 然后 fallback 到默认。这个坑是 prompt template mismatch 的一种——本地推理引擎对 model id 的解析比 API 严格得多。
2. docker-compose.yml
直接复用 Vicki 贴的版本,删了不用的 env:
services:pi:build:context: .dockerfile: Dockerfileimage: pi-agent:0.74.0init: truestdin_open: truetty: trueextra_hosts:- "host.docker.internal:host-gateway"environment:ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}OPENAI_API_KEY: ${OPENAI_API_KEY:-not-needed}OPENAI_API_BASE: ${OPENAI_API_BASE:-http://host.docker.internal:1234/v1}volumes:- ${HOME}/.pi/agent/models.json:/config/models.json- ${WORKSPACE:-.}:/workspace- pi-config:/config- pi-sessions:/sessionsworking_dir: /workspacevolumes:pi-config:pi-sessions:
3. 启动脚本
pi.sh 关键逻辑是 sandbox 开关和容器命名:
#!/usr/bin/env bash
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
WORKSPACE_DIR="${WORKSPACE:-$(pwd)}"
export WORKSPACE="$WORKSPACE_DIR"sandbox="${PI_SANDBOX:-0}"
compose_files=(-f "$SCRIPT_DIR/docker-compose.yml")
if [[ "$sandbox" == "1" ]]; thencompose_files+=(-f "$SCRIPT_DIR/docker-compose.sandbox.yml")
firepo_slug="$(basename -- "$WORKSPACE_DIR" | tr -c 'a-zA-Z0-9_.-' '-' | sed 's/^-*//')"
[[ -z "$repo_slug" ]] && repo_slug="workspace"
container_name="pi-${repo_slug}-$$"cmd=(docker compose --project-directory "$SCRIPT_DIR""${compose_files[@]}" run --rm --name "$container_name" pi)
exec "${cmd[@]}"
--sandbox=1 会叠加 docker-compose.sandbox.yml,进一步把网络和文件系统 read-only 化。我跑日常开发任务只用默认,跑陌生 repo 才加 sandbox。
三、实测的几个具体任务
任务 A:把一个 notebook 重构成 5 个模块
输入:pricing.ipynb(约 380 行,包含散落的 pd.read_csv、几段 ad-hoc 可视化代码)。
本地模型表现:
- 第 1 轮:建议分成
data_loader.py/pricing_model.py/viz.py/cli.py/__init__.py,方向对 - 第 2 轮:把 cell 之间的隐式状态显式化成
PricingInputdataclass,结果可以直接from pricing_model import PricingInput用 - 第 3 轮:补
pyproject.toml+ruff+pytest的最小骨架,跑ruff check .一次性过
耗时:Gemma-4 12B QAT 在 M2 上大概 4 分 20 秒完成整体重构。Claude Sonnet 4.5 同样任务大约 1 分 40 秒(同样在 64 GB 内存下的 macOS,本地推理速度受限于 unified memory 带宽)。
任务 B:补 type hints + 写 unit tests
对一个内部 SDK 的 12 个函数补 Generic[T] 类型注解,并补 pytest。
本地模型表现:
- 类型注解:80% 一次过,剩下 20% 漏掉了
Callable参数的 protocol 声明,需要二次提示 - unit tests:每个函数生成 2-3 个 case,覆盖了 happy path 和 1 个 edge case,但没有生成
pytest.raises的异常 case(这是本地模型目前稳定的盲区,需要在 system prompt 里显式强调)
任务 C:bootstrap 一个推荐系统的两塔模型
空白 repo,本地模型自己生成完整脚手架。结果是 FastAPI + Faiss + 一份 README,能跑通 python train.py 但 embedding 模型用的是 all-MiniLM-L6-v2 而不是更合适的 bge-large。这个 trade-off 是合理的:本地模型知道 Faiss 的基础用法,但没主动选更好的 embedding 模型,需要人在 review 时指出。
四、目前看得到的局限
-
推理速度受 unified memory 带宽限制:M2 / 64 GB 上 Gemma-4 12B QAT 大概 18-22 tok/s,26B A4B 降到 9-12 tok/s。CPU-bound 任务的体感差距比预期小,因为模型权重大部分时间在 swap,瓶颈是 RAM 带宽而不是 GPU 算力。
-
Context window 受硬件约束:K-V cache 在长 session 里能涨到 30 GB RAM,再大 Pi 会被 OOM kill。这是为什么 Pi 默认会定期压缩 session 历史。
-
prompt template mismatch 偶发:Gemma 4 在 LM Studio 里默认
chatmltemplate,但有个早期版本用了gemmatemplate,导致 reasoning 步骤被吞了一半。看输出明显变短但又说不清哪不对,调 template 标签后立刻正常。 -
多模态能力有限:Gemma-4 12B QAT 支持 image input,但屏幕截图的 OCR 准确率明显低于 Claude/GPT-5。我用它做过一次 Pi session log 的"看图复盘",识别截图里的命令只对了 60% 左右。
-
生态依赖 HuggingFace / Ollama / LM Studio 任一方的更新节奏:某个版本 LM Studio 把默认 chat template 改了,Pi 那边没同步跟,本地模型连续 3 天输出的 reasoning 全错。这种问题 API 模型基本不会遇到。
五、什么时候值得切本地、什么时候切回去
我自己的切换标准(仅供参考,每个人工作流不一样):
| 任务类型 | 本地(Gemma-4 12B QAT) | API(Claude Sonnet / GPT-5) |
|---|---|---|
| 单元测试补全 | ✅ 默认 | 异常 case 再调 |
| 类型注解补全 | ✅ | 复杂 protocol 调 |
| 小型 refactor(<500 行) | ✅ | |
| 大型重构(跨文件 > 5) | ✅ | |
| 架构设计 / 选型 | ✅ | |
| 排查陌生 repo 的 bug | 辅助 | ✅ 主用 |
| 网络受限/隐私敏感 | ✅ 唯一选项 |
六、给想动手的人几点建议
- 先用 LM Studio 而不是 Ollama:LM Studio 的模型管理 UI 在 macOS 上更直观,GGUF split 自动处理,prompt template 在模型卡片里就能改。Ollama 适合脚本化部署,但第一次跑会卡在
OLLAMA_HOST。 - Pi 不是唯一选择:Aider、Continue、Cline 都支持 OpenAI-compatible endpoint,可以挑一个你顺手的 harness 接入。Pi 的优势是 Docker sandbox 开箱即用。
- quantization 不是越狠越好:QAT 比 INT4 在 reasoning 任务上明显更好;INT8 几乎不掉点;INT3/INT2 不推荐,refactor 任务会大量 hallucination。
- Docker 是必要不是可选:本地 agentic harness 一旦跑出
rm -rf $WORKSPACE或者 curl 一个奇怪 URL,没有隔离就只能重装系统。Pi 默认 Docker 但还是给 bash 完整权限,加--sandbox=1才能网络隔离。
七、参考
- Vickiboykis, Running local models is good now, https://vickiboykis.com/2026/06/15/running-local-models-is-good-now/ —— HN 917 分 / 391 评论
- LM Studio 模型库:https://lmstudio.ai/models
- Pi agent harness:https://github.com/mariozechner/pi-coding-agent
- HuggingFace Gemma-4 卡片:https://huggingface.co/google/gemma-4-12b-qat
- HN 讨论主贴:https://news.ycombinator.com/item?id=48555993
声明:本文实测基于 M2 MacBook Pro / 64 GB RAM / 1 TB SSD / macOS 15.5,LM Studio 0.3.18 + Pi 0.74.0 + Gemma-4 12B QAT (commit
gemma-4-12b-qat-2026-06-12)。不同硬件上推理速度和 context window 表现会有显著差异。
