缓存时间:
2026/05/11 00:46
# 在拥有 24GB 内存的 M4 芯片上运行本地模型
来源:https://jola.dev/posts/running-local-models-on-m4
我断断续续地尝试运行本地模型有一段时间了,终于找到了一种看起来相当可行的配置。虽然它的输出远不及最先进(SOTA)的模型,但能够拥有一个本地模型来处理基本任务、研究和规划,这种兴奋感足以弥补一切!无需互联网连接!更不用说,这也是减少对美国大型科技公司依赖的一种方式,哪怕只是微不足道的一点。
不过我得说,设置这些东西并不简单。首先,你必须选择如何运行模型:Ollama (https://ollama.com/)、llama.cpp (https://github.com/ggml-org/llama.cpp) 或 LM Studio (https://lmstudio.ai/)。每一个都有其独特的怪癖和局限性,而且它们并不提供完全相同的模型。当然,你还得挑选你的模型。你想要的是能在内存中运行且仍留有余地来运行你日常使用的各种 Electron 应用程序的最佳可用模型,此外,该模型至少需要支持 64K 的上下文窗口,理想情况下是 128K 或更多。最近我尝试过 Qwen 3.6 Q3、GPT-OSS 20B 和 Devstral Small 24B,虽然理论上它们都能装入内存,但实际上却无法使用;还有 Gemma 4B,虽然能正常运行,但在工具使用方面表现非常吃力。
接下来是一堆需要微调的配置选项。从比较知名的如温度(temperature),到更晦涩的选项如 K 缓存量化类型(K Cache Quantization Type)。许多工具都带有一套基本的推荐选项,但合适的设置可能取决于你是否启用了思考(thinking)功能等因素!
## Qwen 3.5-9B(4b 量化)
`qwen3.5-9b@q4_k_s`(HuggingFace 链接:https://huggingface.co/unsloth/Qwen3.5-9B-GGUF)是我目前运行效果最好的模型。在 LM Studio 上,它启用了思考功能,成功使用工具,拥有 128K 的上下文窗口,速度约为每秒 40 个 token。与最先进的模型相比,它更容易分心,有时会陷入循环,还会误解请求等。但对于一个能在 24GB 内存的 MacBook Pro 上运行,同时还能留出空间给其他许多程序运行的模型来说,这表现令人惊讶地好!
以下是针对思考模式和编码工作的推荐设置:
> 用于精确编码任务(例如 Web 开发)的思考模式:temperature=0.6, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0
为了启用思考功能,我还必须选择模型,进入配置,滚动到“推理”(Inference)选项卡的底部,并在提示词模板(Prompt Template)中添加 `{% set enable_thinking = true %}`。
我主要通过 pi (https://pi.dev/) 和 OpenCode (https://opencode.ai/) 来使用它。我至今仍未完全决定更喜欢哪一个。Pi 感觉反应更敏捷,但虽然我真的很欣赏其自行构建 harness 以及所有自定义功能的理念,但我忍不住希望它能提供一些合理的默认值。我觉得你很容易会花比实际项目更多的时间去微调你的 pi 配置以使其完美无瑕!
## Pi 设置
以下是 `~/.pi/agent/models.json` 的内容:
```json
{
"providers": {
"lmstudio": {
"baseUrl": "http://localhost:1234/v1",
"api": "openai-completions",
"apiKey": "lm-studio",
"models": [
{
"id": "qwen3.5-9b@q4_k_s",
"reasoning": true,
"compat": { "thinkingFormat": "qwen-chat-template" }
}
]
}
}
}
```
若要隐藏令人分心的思考过程,请在 `~/.pi/agent/settings.json` 中添加 `"hideThinkingBlock": true`。
## OpenCode 设置
`~/.config/opencode/opencode.json`
```json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"lmstudio": {
"npm": "@ai-sdk/openai-compatible",
"name": "LM Studio (local)",
"options": {
"baseURL": "http://127.0.0.1:1234/v1"
},
"models": {
"qwen3.5-9b@q4_k_s": {
"name": "Qwen 3.5 9B Q4_K_S",
"tools": true,
"context_length": 131072,
"max_tokens": 32768
}
}
}
},
"model": "lmstudio/qwen3.5-9b@q4_k_s"
}
```
## 与最先进(SOTA)模型的比较
像 Qwen 3.5 9B (Q4) 这样的模型,在长时间独立解决复杂问题的能力上,远不及最先进的模型。指望它一次性构建整个应用程序是没有意义的,你只会徒劳无功,什么也得不到。
有效的方式是采用更具互动性的工作流程,你与模型一步一步地清晰沟通,并给予它大量指导。我相信这对你们许多人来说听起来毫无意义——为什么要使用一个需要你盯着它工作的模型呢?但我确实发现这鼓励我更加投入。使用最先进模型的缺点是,它们使得将所有认知努力外包变得太容易了,即使你试图主动防止这种情况发生。
使用本地模型时,我必须承担更多的思考和规划工作,我必须更加具体,但它仍然可以作为研究助理、橡胶鸭调试法中的那只“鸭子”,以及一个能瞬间回忆大量编程语言细节和命令行调用的人才能。它并不是大型 AI 公司所宣传的那种 10 倍生产力提升,但它确实有所作为,而且很有趣。
为了让你了解哪些有效、哪些无效,以下是一些非常基础的示例。
## 示例 1:
我想将 Elixir linter `credo` 升级到最新版本,结果代码中出现了警告。作为实验,我请 Qwen 看一下。
```text
我:
运行 mix credo --strict 并建议如何解决,不要编辑代码
Qwen:
Credo 警告
测试文件中有 4 个警告。问题在于使用 length/1 来检查列表是否非空。Credo 建议改为与空列表进行比较。
警告:
1. test/jola_dev_web/controllers/blog_controller_test.exs:11
- assert length(conn.assigns.posts) > 0
2. test/jola_dev_web/controllers/blog_controller_test.exs:22
- assert length(conn.assigns.posts) > 0
3. test/jola_dev/blog_test.exs:10
- assert length(posts) > 0
4. test/jola_dev/blog_test.exs:23
- assert length(tags) > 0
如何修复
将每个 length(list) > 0 替换为 list != []:
blog_controller_test.exs:
# 第 11 行
assert conn.assigns.posts != []
# 第 22 行
assert conn.assigns.posts != []
blog_test.exs:
# 第 10 行
assert posts != []
# 第 23 行
assert tags != []
这在 Elixir 中更地道,并避免了计算列表长度的开销。
```
我请 Qwen 进行编辑,它进行了 4 次并行编辑,非常干净。总体而言,这是一个非常简单的任务,我本可以通过在终端和编辑器之间来回切换以获取行号来自己完成。工作量差异不大,但很方便。
## 示例 2:
在升级一些依赖项后,我遇到了一个 dependabot 的 PR,其中存在 git 冲突,而 dependabot 出于某种原因拒绝变基(rebase)。我将其拉取下来,进行了变基,并请 Qwen 查看。这是一个非常简单解决的冲突,只需保留每个较新的版本即可,模型也意识到了这一点。
```text
我看到了冲突。情况如下:
HEAD(你的当前分支):
- sentry: 13.0.1
- tailwind: 0.3.1
另一边(正在变基的提交):
- sentry: 12.0.3
- tailwind: 0.4.1
你的依赖项升级出现了分歧。以下是你的选项:
选项 1:接受两项更新(推荐)
- 保留来自 HEAD 的 sentry 13.0.1
- 保留来自另一边的 tailwind 0.4.1
- 这样可以获得两个依赖项的最新版本
选项 2:仅接受 sentry 更新
- 保留 sentry 13.0.1
- 保留 tailwind 0.3.1
选项 3:仅接受 tailwind 更新
- 保留 sentry 12.0.3
- 保留 tailwind 0.4.1
你想采取哪种方法?
```
然而,当要求它进行更改时,它忘记了进行编辑,而是试图直接添加更改并继续变基,尽管冲突标记仍然存在。
```text
> 思考:用户想要接受两项更新——sentry 13.0.1 和 tailwind 0.4.1。我需要通过保留 sentry 的 HEAD 版本和 tailwind 的另一边版本来解决冲突。我将使用 git add 来暂存已解决的文件。
----
# 暂存已解决的 mix.lock 并继续变基
$ git add mix.lock && git rebase --continue
Vim: 警告:输出未指向终端
Vim: 警告:输入未来自终端
"~/workspace/mixtli/.git/COMMIT_EDITMSG" 32L, 1047B
从 0.3.1 升级 tailwind 至 0.4.1
将 tailwind (https://github.com/phoenixframework/tailwind) 从 0.3.1 升级到 0.44.1。
- 更新日志 (https://github.com/phoenixframework/tailwind/blob/main/CHANGELOG.mmd)
- 提交记录 (https://github.com/phoenixframework/tailwind/compare/v0.3.1...v0.4.11)
```
此外,它未能识别 `git rebase --continue` 会打开编辑器,随后 OpenCode 陷入了挂起状态,尽管这可能只是一次性的问题。
## 结论
总之,大型语言模型(LLM)具有极强的灵活性,即使是本地模型也能做成千上万的事情。唯一的限制是你的想象力。本地模型存在明显的权衡,但它们带来了一些颇具吸引力的好处:
- 无需互联网连接,你可以在飞机上工作!
- 成本仅限于你使用的电费,假设你本来就要买电脑。无需订阅费用。
- 训练这些模型仍然会产生严重的环境成本,但开源模型公司在环境影响方面远未名列前茅,而且使用自己的硬件意味着减少数据中心的使用。
- tinkering( tinkering / 折腾)本身很有趣。
LLM 对我们的世界产生了巨大影响,其中很多并不好,但显然它们将长期存在。尝试使用本地模型感觉是一种更可持续、更积极的与这项技术互动的方式。老实说,即使它做错了事,这也很有趣!