为 AI 辅助开发调优一台 Linux 笔记本
我在一台运行 Ubuntu 的 Dell 笔记本上写软件。现在大部分工作都通过 Claude Code 完成。它整天运行构建、测试和数据库。十一月的一个周日,我问了它一个简单的问题:这台笔记本还能更好吗?
我原以为只会做几处小调整。结果我们发现了真正的问题,还有几个意外。“我们”指的是运行 Opus 的 Claude Code 和我。大部分挖掘工作是它做的。它不能使用 sudo,所以它给我命令去运行。这些是我的笔记。
太长不看
| 之前 | 之后 | |
|---|---|---|
| 磁盘使用率 | 90% | 25% |
| 启动时间 | 2 分 10 秒 | 20 秒 |
| 每次启动 SSD 冻结 30 秒的次数 | 5 到 68 | 此后没有 |
| NVIDIA 显卡空闲时 | 唤醒,约 10 W | 97% 的时间休眠 |
| 一次 Postgres 测试查询 | 605 ms | 330 ms |
| 测试数据库中的一次提交 | 0.58 ms | 0.04 ms |
| 完整测试运行 | 283 s | 198 s |
这台机器
这台笔记本是 Dell Precision 3591,有 32 GB 内存和一块 1 TB 的 Micron SSD。显卡是一块小型 NVIDIA RTX 1000 Ada,有 6 GB 显存。处理器是 Intel Core Ultra 7 155H。它还有一个内置的 AI 芯片,即 NPU。我从未用过它。
处理器有 16 个核心和 22 个线程。六个核心是快速的“性能”核。另外十个是较慢的“能效”核。我之前不知道同一个芯片里的核心可以不同。这是硬件,不是设置:Intel 从 2021 年起就混合了这两种核心。快速核心每个运行两个线程。小核心耗电少得多。内核决定哪些工作去哪里,这一点后面还会提到。
一块 90% 满的磁盘
第一个发现最简单。磁盘满了 90%,一个文件夹占了 431 GB。
那个文件夹存放着一个 Rust 项目 Morpholog 的调试构建。Morpholog 是我的开源项目。企业把规则写一次,Morpholog 就根据这些规则检查每一次变更。它用 Rust 编写,并针对 Postgres 进行测试。
Cargo 是 Rust 的构建工具,它保留自己做过的每一次构建。它从不丢弃旧的构建。每次我切换分支,代码就变了,于是 Cargo 构建并保留一份新副本。几个月下来就填满了磁盘。
删除那个文件夹后,磁盘占用从 90% 降到了 25%。但它很快又涨回来。几个小时内就又到了 45 GB。于是我们用 cargo-sweep 设置了一个每晚运行的任务。它会删除 14 天没人使用过的构建输出。它还会让每个项目的构建文件夹保持在 60 GB 以下。
一块冻结 30 秒的 SSD
第二个发现更重要。内核日志里全是这样的行:
nvme nvme0: I/O tag 849 (a351) QID 3 timeout, completion polled
每一行都意味着一次 30 秒的冻结。当 SSD 完成一次读或写时,它会发送一个称为中断的信号。在这里,信号丢失了。内核等了 30 秒,放弃了,然后自己检查驱动器。答案一直就在那里。每次启动会发生 5 到 68 次,而且从九月下旬就开始了。
原因是 Intel VMD,即 Volume Management Device。它位于处理器和 SSD 之间。这台笔记本出厂时 VMD 是开启的,处于一种称为“RAID On”的存储模式。在 Linux 上,VMD 有时会丢掉驱动器的中断。其他人在其他笔记本的 Micron 硬盘上也见过同样的问题。他们通过关闭 VMD 解决了它。
早在九月,我试过一个内核参数 pcie_aspm=off。它会关闭 PCIe 链路的省电功能,这些链路连接着 SSD 和 GPU。它没有帮助。它还有一个副作用,下面会提到。
我这台笔记本不用 Windows,所以修复很简单。在 BIOS 中,我把存储模式从“RAID On”改成了“AHCI/NVMe”。在这种模式下,Linux 直接与驱动器通信。Ubuntu 像以前一样启动。启动时间从 2 分 10 秒降到了 20 秒。此后没有再出现超时。而且整台笔记本都感觉更快了,不只是构建。每个程序都一直在等同一块磁盘。
为 AI 辅助开发调优 Linux 笔记本电脑
我们还检查了硬盘的健康状况。它已用掉额定寿命的 3%。它写入了 37.2 TB,读取了 7 TB,也就是说每读取一次就有五次写入。几乎所有那些写入都来自 Rust 构建。
如果你有一台较新的 Intel 笔记本电脑,journalctl -k -b | grep -c 'completion polled' 会统计自启动以来这些冻结的次数。
一块从不休眠的 GPU
那个旧的内核标志是有代价的。它让 NVIDIA 显卡保持唤醒。这张卡在什么都不做时消耗约 10 W。在笔记本电脑里,那是处理器本可以利用的热量。我们移除该标志后,显卡有 97% 的时间处于休眠。
这里有个小惊喜。用 nvidia-smi 询问显卡会把它唤醒,因为该工具必须与显卡通信。所以我最初的读数显示 GPU 很忙:因为我一直在询问。内核保留着自己的记录,读取它不会唤醒显卡。
负载下的热量
在一次长时间构建期间,处理器温度保持在 99 到 100 °C。这让我担心。我最初的读数说核心已降速到 1.2 GHz。
那个读数是错的。它把空闲核心也平均了进去,而这些核心按设计就运行得慢。我们改做了一个干净的测试,让每个核心都忙一分钟。快核心保持在 2.9 GHz,慢核心保持在 2.3 GHz。风扇转速为 4,200 rpm。对于 15 英寸笔记本电脑里的这颗芯片,这是正常的。没有什么需要修复。
教训是:测量正在工作的核心,而不是所有核心。
内核设置
有些设置对于这类工作来说太小或太保守。
- 文件监视。编辑器和测试运行器会请求内核在文件发生变化时通知它们。它们跟踪的每个文件使用一个“监视”。我的笔记本电脑允许 65,536 个。几个大型仓库就能用完,然后工具会悄悄停止注意到变化。我们将其提高到约一百万。
- Swappiness。这告诉内核它应该多积极地将会存移到磁盘。我们将其从 60 降到 10,这样内存会保留在 RAM 中,除非空间真的耗尽。
- 压缩交换。zswap 在内存写入磁盘之前先压缩它,这比普通交换快得多。我在内核命令行上请求使用 zstd 压缩器。内核却悄悄用了 lzo。zstd 是一个模块,在启动那么早的时候还没有加载。稍后用 systemd tmpfiles 规则设置它,就成功了。值得回读内核实际选择了什么。
- 崩溃转储。一个崩溃转储服务保留 512 MB 的 RAM 用于内核崩溃报告。我从不发送这些,所以我们移除了它。
出厂设置下的 Postgres
笔记本电脑上的两个 Postgres 服务器都运行在默认设置下。这意味着在 32 GB 的机器上只有 128 MB 的共享内存。这意味着成本估算还是为旋转磁盘写的。还意味着 JIT 是开启的。JIT 在运行查询之前将其转换为机器码。这对长报告有回报。对于测试运行的短查询来说,这是浪费工作。
我们给 Postgres 2 GB 共享内存,并告诉它磁盘是 SSD。我们关闭了 JIT,并让它在一次查询中使用更多核心。一个带连接和求和的测试查询从 605 ms 降到 330 ms。
写入仍然一样慢。每次提交都要等到它安全落到磁盘上,而这个等待决定了节奏。所以,仅对测试数据库,我们关闭了 synchronous_commit。然后 Postgres 会在把提交写入磁盘之前就确认它。崩溃可能会丢失最后零点几秒。它不会损坏任何东西,因为 Postgres 仍然按顺序写日志。提交变得快了 15 倍:0.04 ms 而不是 0.58 ms。
我们用于基准测试的数据库保留了安全设置。这样,它们的数字仍然可以与更早的运行比较。
并行运行测试
Morpholog 的完整测试运行大约花了五分钟。其中大部分时间,一个核心在工作,21 个在等待。数据库测试共享一个数据库,并在开始时清空它。所以它们不得不轮流进行。
修复方法是给每个测试程序它自己的数据库副本。Postgres 可以在 79 ms 内从模板复制一个小数据库。有了自己的副本,测试就可以并排运行。
这带来了两个意外发现。第一个在 Postgres 里。删除一个数据库用了 1.3 秒,因为它会强制触发一次 checkpoint。checkpoint 会把所有待处理的更改写入磁盘,这样就不会有东西还指向已删除的文件。每个测试程序删一次库会花上几分钟。所以现在副本会在运行结束后一起删除,每次 16 个。150 次删除用了 20 秒,而不是大约 195 秒。
从模板创建的副本也会丢失通过 ALTER DATABASE 做的任何设置。所以 fast-commit 设置必须在每个副本上重新设置。
第二个意外在测试本身。有五个查询会扫描整个 Postgres 服务器,而不只是自己的数据库。有些会统计等待锁的会话。有一个会统计锁。每个测试单独运行时都能通过。并排运行时,它们还会把彼此的会话也统计进去。现在这些查询只查看自己的数据库。
并行运行还发现了一个真正的 bug。Postgres 会保留已完成的 serializable transaction 的一些锁。它会一直持有这些锁,直到服务器上任意数据库中所有重叠的读写事务都已结束。有五个测试统计了这些锁。当其他测试同时在旁边运行时,它们统计到了残留锁,于是失败。它们在主分支上也可能失败,只要另一个 serializable 会话处于打开状态。现在它们只统计自己两个会话的锁。
最终,完整测试运行用了 198 秒,而不是 283 秒。两次运行使用同一个 commit,且没有其他任务在运行。
6 GB 显卡上的本地模型
我笔记本上的 Ollama 装着一个旧的 120 亿参数模型,大小 9 GB。我的 GPU 有 6 GB 内存,所以这个模型总有一部分跑在处理器上,速度慢得多。我们把它换成了 Qwen 3.5 4B。它是 3.3 GB,完全能放进 GPU,每秒写出 25 到 39 个 token。
当我达到每周限额时,它能代替 Claude 吗?Ollama 现在使用与 Claude Code 相同的 API,所以可以直接接入。但 Ollama 的指南说,要给 Claude Code 64,000 个 token 或更多的上下文。在这个大小下,模型的四分之一会溢出到处理器上。而且 4B 模型无法记住大型代码库的规则。对于真正的工作,它无法替代。云端更大的开放模型会是更好的后备方案。
我现在最常用的模型是最小的那个。Qwen3 Embedding 0.6B 是 639 MB。它把一段文本变成一串数字,也就是一个向量。含义相似的文本会得到相似的向量,因此搜索可以匹配含义,而不只是词。它每秒处理大约 12 个代码块。Morpholog 的全部源代码大约 104,000 行,会花三四分钟。
按含义搜索
编码代理会花很多 token 只是为了找到路。它们搜索、打开整个文件,然后再搜索。所以我们搭了 grepai,一个开源的代码搜索工具。它把每个仓库切成块,并为每一块存储一个向量。向量存在 Postgres 里,使用 pgvector 扩展。一次搜索会混合含义与精确的词。一个后台服务会在文件变化时保持索引最新。
现在还早,但两个教训很快就来了。生成的文件淹没了真正的答案:测试结果和 JSON schema 排在最前面。排除它们后解决了这个问题。而且 grepai 跳过了 Morpholog 的规则文件,因为它不认识它们的扩展名。我们加上之后,一个关于某条规则的问题会直接定位到那条规则。我会再汇报它是否真的节省 token。
让后台工作避开快核心
然后发生了件怪事。另一个 Claude Code 会话正在给 Morpholog 的测试计时。它的数字突然翻倍。它去寻找原因,发现 Ollama 的模型进程 llama-server 占用了两个 CPU 核心。
那是我们正在构建的新搜索索引。模型完全运行在 GPU 上。即便如此,给它喂任务也要占用两个核心,而且它挑的是快的那些。
修复办法是 Ollama 的 systemd 设置里的一行:CPUAffinity=12-21。这会让 Ollama 留在能效核上。我们把同一行也给了索引器、夜间备份和构建清理。快速核则留给构建和测试。
NPU
NPU 是处理器内置的 AI 芯片。Intel 给出的额定性能约为 11 TOPS,即每秒万亿次运算。Linux 会加载它的驱动,但我笔记本上没有任何东西用到它。有人报告通过 Intel 的 OpenVINO 在它上面运行 Whisper 效果不错。在它上面跑语言模型仍然很慢。RTX 能完成我需要的所有事情,而且更快。所以目前我先不动 NPU。用电池时,它可能值得再看一眼。
读日志
那天还产出了一个小工具。一份测试日志可以达到 250 KB。智能体会把它全部读完,只为找到一个错误。所以我们写了 logdigest,一个短脚本。它只打印真正重要的行:失败、panic、编译器错误、traceback 和最终结果。然后智能体只在它指出的地方打开完整日志。
回顾
这些里没多少是聪明办法。大多数都归结为默认值,是为另一台机器或另一项工作选的。最糟的问题已经存在七周,而我从未看过。
笔记本现在 20 秒启动。磁盘只用了四分之一,GPU 空闲时会休眠。SSD 不再一次停住 30 秒。Claude Code 在一个下午就找到了大部分问题。它只需要我来执行 sudo。