Searching LIGO data for 20 cents a week
divyanshgolyan.com/posts/ligo-20-cents/
The next interesting thing in LIGO’s data might be hiding in a search nobody has run because it costs too much. LIGO, Virgo and KAGRA used about 700 million CPU core-hours in a single year of their last observing run. So I tried to make the search cheap. It hasn’t found anything new yet, but it’s now cheap enough to try what nobody has.
Mine searches a week of data from both LIGO detectors for about $0.20 on a rented GPU. PyCBC, one of LIGO’s standard pipelines, needs 182 CPU core-hours for the same week. Here’s how it got there, one step at a time.
A search is a giant pattern match
Physicists can predict the chirp that two merging black holes make, so the search keeps a few thousand predicted chirps, called templates, and slides each one across every moment of the data, looking for a fit. The fit score is the signal-to-noise ratio, or SNR: how much louder the match is than noise alone would make it. Noise scores 1 to 3. Real mergers score 10 to 30.
But noise sometimes fakes a fit, so the search needs to know how often. A real signal reaches both LIGO detectors within milliseconds. So the search shifts one detector’s data by a few seconds, where nothing real can line up in both, and counts the fits it still finds (false positives).
Every search does three things to each stretch of data:
- Clean it. The raw data rumbles at low pitches and has spikes. Cleaning cuts the rumble, mutes the spikes and measures how noisy each pitch is, so every pitch counts fairly.
- Search it. Slide every template across the cleaned data and score the fit at each moment.
- Check the loud fits. A glitch can score high without looking like a chirp. So for each loud fit, the search splits the template into pieces by pitch and asks whether each piece fits as well as it should. A real chirp fits evenly. A glitch dumps its score into one piece.
PyCBC does step 2 one template at a time, on CPUs: 182 core-hours a week. Moving it to a GPU helps less than you’d think. In my test, its GPU mode searched 16x faster, but each job got only 2x faster, because the cleaning still ran on the CPU, repeated for every slice of the templates. Tuned well, I project it at about $0.72 a week, the hollow dot. Getting below that took three changes.
$0.52: one big matrix multiply
Step 2 is the expensive one. Sliding a template across every moment sounds slow, but a Fourier transform (FFT) does every slide at once. The standard way runs one FFT per template, thousands for every stretch of data.
Now, most templates look alike. Compress them with a singular value decomposition (SVD, a standard way to find the shapes a set of signals shares) and a few basic shapes can rebuild all of them, the way a JPEG stores a few patterns plus weights instead of every pixel. The search then needs one FFT per shape instead of one per template. Rebuilding every template’s score from the shapes’ scores is one large matrix multiply (which is exactly what GPUs are good at).
The multiply has a catch. It produces a score for every template at every moment: billions of numbers, almost all noise. Writing them to the GPU’s memory and reading them back takes longer than the math. So I (well, Claude) wrote a GPU kernel that does the multiply and, while the numbers are still on the chip, keeps only the best score in every 128 moments (about 1/16 of a second). It ran about 7x faster than the standard multiply, followed up by a separate pass to find the peaks.
That got a week down to $0.52. But now searching took 4.5 GPU-minutes, and checking took 34.
$0.30: exact checks only when loud
The checks ran on every fit above SNR 5, millions a week, most of them noise. But the compressed search already gives every fit a quick score, and for 99 fits in 100 it lands within 1% of the exact one. So the quiet fits keep their quick score, and only loud fits get the exact score and the glitch checks. On my test data, that cut the fits getting exact checks from 2.1 million to 0.33 million.
Each check was slow too, because it read a whole stored template from memory. Now the chip rebuilds the template from a few stored points, which made the checks 11x faster. This is a different compression from the SVD: that one shares shapes across templates, while this one shrinks each template itself.
Together, that brought a week to $0.30. Now every step was fast, but the GPU sat idle most of the time.
$0.142: keep the GPU busy
It was waiting: for downloads, for the CPU to clean the next batch, or for files to be written. It was busy 27% of the time, and I was paying for 100% (mom would be very disappointed).
The biggest fix by far: the GPU made 200 million weak fits per batch, then sat idle while the CPU sorted and threw most of them away. Now the GPU drops them itself, and 23 million reach the CPU. Cleaning also moved onto the GPU, from 26 to 39 seconds a piece to 3.
Looking back, every fix so far had the same thing in common.
Memory, not math
Each one moved less data, not less math. On an A40, the big FFTs used about 5% of the card’s math power. The rest of the time, the cores waited for data from memory.
That also decides which GPU to rent: the one with the most memory speed per dollar. In my tests, an RTX 4090, a gaming card, was 1.6 times cheaper per result than the A40. The H100 was the fastest card I tried, and 3.1 times more expensive per result.
| Card | GB/s per $ an hour | Cost per result |
|---|---|---|
| RTX 4090 | 1,024 | 0.62x |
| A40 | 944 | 1x |
| H100 | 744 | 3.1x |
But a cheap search is worthless if it misses things.
99 of 101
I ran mine over all of O4a and O4b, the 232 days in which both LIGO detectors were recording. PyCBC found 101 mergers there with high confidence, and mine found 99 of them.
Both misses were borderline. My search saw GW241116_151753 in both of its template sets, but running two sets costs a small penalty, and that pushed it just over 1 false alarm a year. It also had data from Virgo, the European detector, which I don’t use. PyCBC was the only pipeline sure about GW231119_075248: GstLAL and MBTA, two other LIGO pipelines, put it at 0.5 and 1.9 false alarms a year, and mine at 6.
Nothing new, yet
Sadly, it found nothing new in O4a and O4b. But O4c comes out on 16 December. At 20 cents a week, I can run the searches nobody has tried!
How I measured
My cost is the steady rate. Filtering one week of data from both LIGO detectors takes 868 GPU-seconds on an Nvidia A40 at $0.59 an hour, which is $0.142; ranking and the rest bring it to about $0.20. The steps in the first figure are from my lab notes: $0.52 is the first full week, $0.30 is one day scaled to a week, and $0.142 is the steady rate. The $0.52 and $0.30 steps searched only my 4,612 standard templates; $0.142 and PyCBC’s 182 core-hours cover all 5,880, including 1,268 for oval orbits. The full O4a and O4b run cost about $11 as billed, including restarts and tests, and O4b ran at $0.26 a week because its GPUs waited on downloads. PyCBC’s 182 core-hours is measured with PyCBC 2.8.3 on an Intel Xeon Gold 6342; it agrees with the published figure in Soni & Nitz, Table 4. The GPU projection comes from 8 PyCBC processes sharing one A40, with the repeated cleaning removed. The GPU comparison is from a kernel test, with the 4090 at RunPod’s $0.89 an hour; on O4b, an RTX 3090 at $0.19 an hour searched a week for $0.141, one machine over two weeks. “High confidence” means PyCBC’s false-alarm rate is below 1 per 100 years, from the GWOSC event pages on 29 October 2026. I used only the two LIGO detectors, not Virgo. Code, data and results are on GitHub and archived at doi:10.5281/zenodo.23761181.