# Tuning a Linux laptop for AI-assisted development

> Source: <https://a115.co.uk/tuning-a-linux-laptop-for-ai-assisted-development/>
> Published: 2026-11-15 17:15:35+00:00

[Jordan Dimov](https://a115.co.uk/about/jordan-dimov.html). Published 15 November 2026.

I write software on a Dell laptop running Ubuntu. Most of that work now goes through Claude Code. It runs builds, tests and databases all day. One Sunday in November, I asked it a simple question: could this laptop do better?

I expected a few small tweaks. We found real problems instead, and a few surprises. "We" here means Claude Code, running Opus, and me. It did most of the digging. It could not use sudo, so it gave me commands to run. These are my notes.

## TL/DR

|  | Before | After | 
|---|---|---|
| Disk in use | 90% | 25% | 
| Boot time | 2 min 10 s | 20 s | 
| SSD freezes of 30 seconds, per boot | 5 to 68 | none since | 
| NVIDIA card when idle | awake, about 10 W | asleep 97% of the time | 
| A Postgres test query | 605 ms | 330 ms | 
| A commit in the test database | 0.58 ms | 0.04 ms | 
| Full test run | 283 s | 198 s | 

## The machine

The laptop is a Dell Precision 3591, with 32 GB of RAM and a 1 TB Micron SSD. The graphics card is a small NVIDIA RTX 1000 Ada, with 6 GB of memory. The processor is an Intel Core Ultra 7 155H. It also has a built-in AI chip, an NPU. I had never used it.

The processor has 16 cores and 22 threads. Six cores are fast "performance" cores. The other ten are slower "efficiency" cores. I had not known that the cores in one chip could differ. It is the hardware, not a setting: Intel has mixed the two kinds since 2021. The fast cores run two threads each. The small ones use far less power. The kernel decides which work goes where, and that comes up again later.

## A disk 90% full

The first find was the easiest. The disk was 90% full, and one folder held 431 GB.

That folder held the debug builds of one Rust project, [Morpholog](https://a115.co.uk/morpholog/). Morpholog is my open-source project. A business writes its rules once, and Morpholog checks every change against them. It is written in Rust and tested against Postgres.

Cargo, Rust's build tool, keeps every build it has made. It never throws old ones away. Each time I switched branches, the code changed, so Cargo built and kept a new copy. Months of that filled the disk.

Deleting the folder took the disk from 90% to 25% full. But it grows back fast. Within hours it was at 45 GB again. So we set up a nightly job with `cargo-sweep`. It removes build output nobody has used for 14 days. It also keeps each project's build folder under 60 GB.

## An SSD that froze for 30 seconds

The second find mattered more. The kernel log was full of lines like this:

```
nvme nvme0: I/O tag 849 (a351) QID 3 timeout, completion polled
```

Each line meant a 30-second freeze. When the SSD finishes a read or a write, it sends a signal called an interrupt. Here, the signal got lost. The kernel waited 30 seconds, gave up, and checked the drive itself. The answer had been sitting there all along. This happened between 5 and 68 times per boot, and it had gone on since late September.

The cause was Intel VMD, the Volume Management Device. It sits between the processor and the SSD. This laptop came with VMD switched on, under a storage mode called "RAID On". On Linux, VMD sometimes drops the drive's interrupts. [Other people have seen the same](https://discussion.fedoraproject.org/t/nvme-i-o-timeouts-and-slow-boot-on-asus-zenbook-with-micron-mtfdkba1t0qgn/187890) with Micron drives in other laptops. [They fixed it by turning VMD off](https://forum.level1techs.com/t/resolved-but-confused-linux-intel-vmd-micron-ssd-btrfs/255663).

Back in September, I had tried a kernel flag, `pcie_aspm=off`. It turns off power saving on the PCIe links, the lanes that connect the SSD and the GPU. It did not help. It also had a side effect, which comes up below.

I don't use Windows on this laptop, so the fix was simple. In the BIOS, I changed the storage mode from "RAID On" to "AHCI/NVMe". In that mode, Linux talks to the drive directly. Ubuntu started as before. Boot time fell from 2 minutes 10 seconds to 20 seconds. There have been no timeouts since. And the whole laptop felt snappier, not just the builds. Every program had been waiting on the same disk.

We also checked the drive's health. It had used 3% of its rated life. It had written 37.2 TB and read 7 TB, so five writes for every read. Almost all of those writes were Rust builds.

If you have a recent Intel laptop, `journalctl -k -b | grep -c 'completion polled'` counts these freezes since boot.

## A GPU that never slept

That old kernel flag had a cost. It kept the NVIDIA card awake. The card drew about 10 W while doing nothing. In a laptop, that is heat the processor could have used. Once we removed the flag, the card slept 97% of the time.

There was a small surprise here. Asking `nvidia-smi` about the card wakes it up, because the tool has to talk to the card. So my first readings showed a busy GPU: I kept asking. The kernel keeps its own record, and reading that does not wake the card.

## Heat under load

During a long build, the processor sat at 99 to 100 °C. That worried me. My first reading said the cores had slowed to 1.2 GHz.

That reading was wrong. It averaged in idle cores, which run slowly by design. We ran a clean test instead, with every core busy for a minute. The fast cores held 2.9 GHz and the slow ones 2.3 GHz. The fan ran at 4,200 rpm. For this chip in a 15-inch laptop, that is normal. There was nothing to fix.

The lesson: measure the cores that are working, not all of them.

## Kernel settings

Some settings were too small or too cautious for this kind of work.

- **File watches.** Editors and test runners ask the kernel to tell them when a file changes. Each file they follow uses one "watch". My laptop allowed 65,536. A few large repositories can use that up, and then tools quietly stop noticing changes. We raised it to about a million.
- **Swappiness.** This tells the kernel how keen it should be to move memory out to disk. We lowered it from 60 to 10, so memory stays in RAM unless space really runs out.
- **Compressed swap.** zswap squeezes memory before it goes to disk, which is much faster than plain swap. I asked for the zstd compressor on the kernel command line. The kernel quietly used lzo instead. zstd is a module, and it had not loaded that early in boot. Setting it a moment later, with a systemd tmpfiles rule, worked. It is worth reading back what the kernel actually chose.
- **Crash dumps.** A crash-dump service kept 512 MB of RAM in reserve for kernel crash reports. I never send those, so we removed it.

## Postgres on factory settings

Both Postgres servers on the laptop ran on their defaults. That meant 128 MB of shared memory on a 32 GB machine. It meant cost estimates written for spinning disks. And it meant JIT was on. JIT turns a query into machine code before running it. That pays off for long reports. For the short queries that tests run, it is wasted work.

We gave Postgres 2 GB of shared memory and told it the disk is an SSD. We turned JIT off and let it use more cores for one query. A test query, with a join and a sum, went from 605 ms to 330 ms.

Writes stayed just as slow. Each commit waits until it is safely on disk, and that wait sets the pace. So, for the test database only, we turned off `synchronous_commit`. Postgres then confirms a commit before writing it to disk. A crash could lose the last fraction of a second. It cannot corrupt anything, because Postgres still writes its log in order. Commits became 15 times faster: 0.04 ms instead of 0.58 ms.

The databases we use for benchmarks kept the safe setting. That way, their numbers still compare with older runs.

## Running the tests in parallel

Morpholog's full test run took about five minutes. For most of that time, one core worked and 21 waited. The database tests shared one database and wiped it at the start. So they had to take turns.

The fix was to give each test program its own copy of the database. Postgres can copy a small database from a template in 79 ms. With their own copies, the tests can run side by side.

This turned up two surprises. The first was in Postgres. Dropping a database took 1.3 seconds, because it forces a checkpoint. A checkpoint writes every pending change to disk, so nothing is left pointing at the deleted files. One drop per test program would have cost minutes. So the copies are now dropped together after the run, 16 at a time. 150 drops took 20 seconds instead of about 195.

A copy made from a template also loses any setting made with `ALTER DATABASE`. So the fast-commit setting has to be set again on each copy.

The second surprise was in the tests themselves. Five queries looked across the whole Postgres server, not only at their own database. Some counted sessions waiting on a lock. One counted the locks. Each test passed when run alone. Run side by side, they would have counted each other's sessions too. Those queries now look only at their own database.

Running in parallel also found a real bug. Postgres keeps some locks from a finished serializable transaction. It holds them until every overlapping read-write transaction has ended, in any database on the server. Five tests counted those locks. With other tests running beside them, they counted leftovers and failed. They could have failed on the main branch too, whenever another serializable session was open. Now they count only the locks of their own two sessions.

In the end, the full test run took 198 seconds instead of 283. Both runs used the same commit, with nothing else running.

## Local models on a 6 GB card

Ollama on my laptop held an old 12-billion-parameter model of 9 GB. My GPU has 6 GB of memory, so part of that model always ran on the processor, much more slowly. We replaced it with Qwen 3.5 4B. It is 3.3 GB, fits on the GPU entirely, and writes 25 to 39 tokens a second.

Could it stand in for Claude when I hit my weekly limit? Ollama now speaks the same API as Claude Code, so it plugs straight in. But Ollama's guide says to give Claude Code 64,000 tokens of context or more. At that size, a quarter of the model spilled onto the processor. And a 4B model cannot keep a large codebase's rules in mind. For real work it is no substitute. A larger open model in the cloud would make a better fallback.

The model I now use most is the smallest one. Qwen3 Embedding 0.6B is 639 MB. It turns a piece of text into a list of numbers, a vector. Texts with similar meanings get similar vectors, so a search can match meaning, not only words. It handled about 12 chunks of code a second. All of Morpholog's source, about 104,000 lines, would take three or four minutes.

## Search by meaning

Coding agents spend a lot of tokens just finding their way around. They search, open whole files, and search again. So we set up grepai, an open-source code search tool. It cuts each repository into chunks and stores a vector for each one. The vectors live in Postgres, with the pgvector extension. A search mixes meaning with exact words. A background service keeps the index up to date as files change.

It is early days, but two lessons came quickly. Generated files drowned the real answers: test results and JSON schemas ranked first. Leaving them out fixed that. And grepai skipped Morpholog's rule files, because it did not know their extension. Once we added it, a question about a rule went straight to that rule. I will report back on whether it saves real tokens.

## Keeping background work off the fast cores

Then something odd happened. Another Claude Code session was timing Morpholog's tests. Its numbers suddenly doubled. It went looking for the cause, and found `llama-server`, Ollama's model process, using two CPU cores.

That was our new search index, being built. The model ran entirely on the GPU. Even so, feeding it work took two cores, and it had picked the fast ones.

The fix was one line in Ollama's systemd settings: `CPUAffinity=12-21`. That keeps Ollama on the efficiency cores. We gave the same line to the indexer, the nightly backup and the build clean-up. The fast cores stay free for builds and tests.

## The NPU

The NPU is the AI chip built into the processor. Intel rates it at about 11 TOPS, trillion operations a second. Linux loads its driver, but nothing on my laptop used it. People report good results running Whisper on it, through Intel's OpenVINO. Language models on it are still slow. The RTX does everything I need, and faster. So I left the NPU alone for now. On battery, it may be worth a second look.

## Reading logs

One small tool came out of the day. A test log can run to 250 KB. An agent will read all of it to find one error. So we wrote `logdigest`, a short script. It prints only the lines that matter: failures, panics, compiler errors, tracebacks and the final result. The agent then opens the full log only where it points.

## Looking back

Little of this was clever. Most of it came down to defaults, chosen for another machine or another job. The worst problem had been there for seven weeks, and I had never looked.

The laptop now starts in 20 seconds. Its disk is a quarter full, and the GPU sleeps when idle. The SSD no longer stops for 30 seconds at a time. Claude Code found most of it in one afternoon. It needed me only for sudo.
