Newshot

为 AI 辅助开发调优 Linux 笔记本

Translation(model translation, not the source's own text)

为 AI 辅助开发调优一台 Linux 笔记本

我在一台运行 Ubuntu 的 Dell 笔记本上写软件。现在大部分工作都通过 Claude Code 完成。它整天运行构建、测试和数据库。十一月的一个周日,我问了它一个简单的问题:这台笔记本还能更好吗?

我原以为只会做几处小调整。结果我们发现了真正的问题,还有几个意外。“我们”指的是运行 Opus 的 Claude Code 和我。大部分挖掘工作是它做的。它不能使用 sudo,所以它给我命令去运行。这些是我的笔记。

太长不看

之前之后
磁盘使用率90%25%
启动时间2 分 10 秒20 秒
每次启动 SSD 冻结 30 秒的次数5 到 68此后没有
NVIDIA 显卡空闲时唤醒,约 10 W97% 的时间休眠
一次 Postgres 测试查询605 ms330 ms
测试数据库中的一次提交0.58 ms0.04 ms
完整测试运行283 s198 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。

The translation is machine-generated and individual terms may be off. Nothing is rewritten or cut; the original is authoritative.

Published by HN AI
This site only summarises and aggregates; copyright stays with the original author. Headlines and summaries follow the source — where they differ, the original wins.
Read the original ↗