Why I built a GPU SQL engine in 2026 — when every other one died
Every standalone GPU database built between 2013 and 2024 was acqui-hired or pivoted. So why ship gpudb in 2026? Because nobody had wired Apple Silicon's unified memory into a SQL engine — and DuckDB hands you a hundred-thousand-user distribution channel without writing a database from scratch.
The graveyard
Every standalone GPU database built between 2013 and 2024 ended in one of three ways: acquisition, layoffs, or quiet abandonment. HEAVY.AI (formerly MapD) was acqui-hired by NVIDIA in 2025. BlazingSQL went dormant. Voltron Data shed half its staff. Brytlyt was absorbed. Kinetica pivoted to vector search and AI. The thesis that you could win in analytics by being faster than CPUs on big aggregates kept running into the same wall: the customers who needed that speed were also the customers who already had a Snowflake contract, a Databricks bill, or a working Postgres they didn't want to migrate off.
So when I started sketching gpudb in early 2026, the first thing every honest reviewer told me was: don't. Build something else. Building "another GPU SQL engine" in 2026 sounds like volunteering to be the next entry in the graveyard.
The reviewers were right about the question. They were wrong about the answer.
The wedge nobody took
If you stare long enough at the postmortems, the dead projects share a shape: they all built standalone databases that competed for the same workload as Snowflake, BigQuery, and the giants. Every one of them was a forklift migration: rip out your warehouse, install ours, get faster queries. The product was technically correct and commercially impossible.
What none of them tried — and what is still empty as of 2026 — is two specific things at the same time:
- Apple Silicon as a real GPU compute target. Nobody has wired Metal or MLX into a published SQL execution engine. cuDF / RAPIDS is CUDA-only. Sirius (UW-Madison + NVIDIA's CIDR 2026 paper, the strongest open GPU OLAP project today) is CUDA-only. HeavyDB is CUDA-only. And Apple Silicon's unified memory architecture — up to 512 GB at 819 GB/s of bandwidth on M3 Ultra, with zero PCIe transfer cost — is a genuine architectural advantage that nobody has put behind a SQL execution path.
- An extension, not a database. DuckDB is the de facto embedded analytical engine of the late 2020s. It has the world's fastest-growing community of analytical-data users, a clean extension API, and a one-line install. Riding it beats competing with it. If you ship as a DuckDB extension, your users don't have to migrate anything: they
LOADyour extension and their existing queries get faster. That removes the single biggest reason every standalone GPU database died.
That combination — Apple Silicon plus DuckDB-extension shape — is the wedge gpudb is built into. Drawn out against the existing field:
| Sirius | cuDF / RAPIDS | HeavyDB | gpudb | |
|---|---|---|---|---|
| Apple Silicon (Metal/MLX) backend | ❌ | ❌ | ❌ | ✅ unique |
| DuckDB-extension shape (no migration) | ✅ | ❌ | ❌ | ✅ |
| CUDA backend | ✅ | ✅ | ✅ | ✅ |
| Hybrid CPU/GPU planner that picks correctly | partial | ❌ | ❌ | ✅ |
| Window functions on GPU | ❌ | partial | ✅ | ✅ planned |
| Apache-2.0 + community-friendly from day 1 | ✅ | ✅ | ✅ | ✅ |
The Apple Silicon row is the structural one. The other rows are catch-up; that one is empty field.
What gpudb actually is
It's a DuckDB loadable extension that registers GPU-backed aggregate functions: gpu_sum, gpu_min, gpu_max, with batched-finalize support so they work inside GROUP BY queries. Underneath, it dispatches to whichever backend is available at runtime: NVIDIA CUDA on Linux + RTX-class hardware, Apple Silicon Metal on M1/M2/M3/M4 macs, and a parallel CPU baseline as the floor.
Because it's a DuckDB extension, your queries do not change shape:
-- Real query, real GPU, real result on RTX 4090 + Apple M4 Max
SELECT gpu_sum(l_orderkey) FROM read_parquet('lineitem.parquet');
[gpudb] registered gpu_sum / gpu_min / gpu_max (backend=CUDA)
gpu_sum(l_orderkey)
18005322964949
That's it. Same DuckDB you were already using. Same parquet on disk. The only difference is one function call name, and the column you're aggregating now flows through a GPU kernel instead of a CPU loop.
Apache-2.0, single repo, builds on both Linux and macOS, pre-alpha but functional. v0.1.3 just shipped — and with it, the milestone that makes the dual-backend thesis credible against a real-world baseline: a hybrid Metal GROUP BY (auto-dispatched between a 32K-partition slot-lock hash aggregate and an optimized multi-pass radix sort) that beats multi-thread DuckDB on 9 of 10 TPC-H lineitem operations on M4 Max. The extension loads directly into the DuckDB CLI from a prebuilt binary — no source build required. curl the .duckdb_extension file, LOAD it, run your query.
Methodology — how every number in this post was measured
Before the numbers, the disclosure that makes them interpretable. Every benchmark below was run on real hardware, against real data (TPC-H or synthetic int64 generated with a fixed seed), with the median of 3–5 runs and the cold/hot path made explicit. The full append-only log is in BENCHMARK.md in the repo; everything in this post is reproducible by cloning the repo, building, and running the named command.
Hardware. Two machines, used for two different lanes of the project:
- Apple M4 Max — 40-core GPU, ~64 GiB unified memory, ~546 GB/s LPDDR5X peak. macOS 15.x, AppleClang 21.0.0, MSL 3.2. This is the Metal lane.
- RTX 4090 Laptop — sm_89, CUDA 13.0, GDDR6X ~1008 GB/s peak. Linux. This is the CUDA lane.
Baselines. The numbers in this post are vs DuckDB CLI v1.5.2 with SET threads=16 (12 P + 4 E cores on M4 Max — the actual user-visible default when someone runs duckdb on their laptop). That's the honest baseline because it's what users actually compare against, not a single-thread C++ loop. The earlier internal benches in BENCHMARK.md use a single-thread scalar baseline (more stringent, isolates the kernel) and the absolute throughput numbers (GiB/s, % of LPDDR5X peak) are run-invariant — but every speedup ratio in this post is vs multi-thread DuckDB.
Measurement. Wall time includes everything: setup, dispatch, kernel execution, host-side post-processing, result return. Kernel time is just the GPU compute portion (the number that approaches the hardware bandwidth ceiling). Both are reported because the gap between them is where future optimization work lives.
Modes. "Hot" means the data is already in the GPU's address space (cached MTLBuffer on Metal, resident in VRAM on CUDA). "Cold" means the GPU has never seen it — we pay either the PCIe transfer (CUDA) or the first-touch unified-memory cost (Metal). Both regimes matter for different real-world query patterns.
SUM, the headline workload
The simplest aggregate. Sum a billion 64-bit integers. There's nothing to be clever about — just memory bandwidth and a reduction. This is the workload that separates "GPU-faster" claims from "GPU-faster-because-we-own-the-bandwidth-ceiling" claims.
That 2.6× number isn't a microbench. It's median of five runs, hot path, 1-billion-row int64 column, vs DuckDB CLI's actual 16-thread default. The Metal kernel sustains 464 GiB/s — 85% of the M4 Max's LPDDR5X peak. The remaining 15% is fixed dispatch overhead and a small amount of host bookkeeping. There is essentially no software performance left for this workload — gpudb is sitting against the silicon ceiling, and DuckDB's 16-thread sweep through DDR5 is what 2.6× slower than that actually looks like.
The TPC-H lineitem SUM numbers fill in the rest of the curve, all vs the same DuckDB 16-thread baseline:
| Workload | DuckDB CPU mt | Metal v0.1.3 | Speedup |
|---|---|---|---|
| 1B int64 SUM HOT | 42 ms | 16.16 ms | 2.6× ✅ |
| 500M int64 SUM HOT | 20 ms | 8.08 ms | 2.5× ✅ |
| 100M int64 SUM HOT | 4 ms | 2.49 ms | 1.6× ✅ |
SF10 SUM l_quantity HOT | 5 ms | 1.16 ms | 4.3× ✅ |
SF10 SUM l_extendedprice HOT | 5 ms | 2.23 ms | 2.2× ✅ |
SF10 SUM l_orderkey HOT | 3 ms | 1.68 ms | 1.8× ✅ |
| 1B int64 SUM COLD (zero-copy) | ~50 ms | 53 ms | ~ ties |
The shape: at scale (≥500M rows), Metal beats DuckDB 2.5–2.6× on streaming SUM. The TPC-H SF10 SUMs (60M rows per column) are smaller workloads where the win narrows but stays positive. The 1B SUM cold path ties DuckDB exactly because Apple Silicon's unified memory pays no transfer cost — the data is just there, and the only difference between cold and hot is which cache line touched it last.
GROUP BY — the v0.1.3 breakthrough
SUM is the bandwidth test. GROUP BY is the algorithm test, and it's the operator that dominates real analytical query time. In v0.1.2, gpudb's Metal GROUP BY used a single multi-pass LSD radix sort, which meant TPC-H SF10 GROUP BY on l_orderkey was 1.78× slower than DuckDB CPU 16-thread. The most-attacked workload in any GPU-DB benchmark, and we were losing it.
v0.1.3 ships a hybrid Metal GROUP BY that auto-dispatches per workload between two paths: a 32K-partition slot-lock hash aggregate (sweet spot at 1024 ≤ unique ≤ 16M) and an optimized multi-pass radix sort (very low or very high cardinality). The most-attacked benchmark flipped from a 1.78× loss to a 1.30× win:
The slot-lock kernel is the breakthrough. It carves the input across 32,768 hash partitions × 1024 threadgroup-memory slots, with a 32-bit CAS protecting non-atomic 64-bit sum updates — a deliberate workaround for Apple Silicon GPUs missing 64-bit atomic_fetch_add until very recent OS versions. Earlier 4K-partition variants saturated around 3M unique groups because per-partition Poisson tails would clip the 1024-slot table; doubling partitions twice to 32K gives a mean of 458 unique-per-partition (load 0.45) with a worst case around 543 — comfortable headroom for TPC-H SF10's 15M-unique l_orderkey.
The radix-opt path remains in the codebase and is what the dispatcher picks at very low or very high cardinalities (where the slot-lock either lock-contends or overflows). Both paths share the same libgpudb dispatch decision frame; the user just gets one fast answer.
The full TPC-H lineitem scorecard
The TPC-H lineitem table is the canonical OLAP benchmark for a reason — it's the workload every analytical engine since the 1990s has been measured against. Here's the full v0.1.3 scorecard on M4 Max, all vs DuckDB CPU 16-thread: 9 wins, 1 structural loss.
Three observations to read out of this scorecard:
- The wins compound at fusion. The top three bars are
SELECT SUM(x), MIN(x), MAX(x), COUNT(x) FROM lineitemfor three different columns. Read the column once, compute four aggregates in a single Metal pass, and the speedup ratio against four-pass CPU code becomes 9.7×, 22×, 25.5× depending on the column's data distribution. The next section unpacks why. - The 1.30× canonical-case win is the most important number on this page. SF10 GROUP BY
l_orderkeyat 15M unique groups is the single benchmark every GPU-database submission gets attacked on, because it's CPU's strongest workload (large hash table, perfect parallelization). v0.1.2 was 1.78× slower than DuckDB on it. v0.1.3's slot-lock kernel flips it to 1.30× faster. That flip is the entire story of v0.1.3. - The single structural loss is documented and won't be fixed. SF10 GROUP BY
l_quantityhas 50 unique groups across 60M rows. CPU's hash table fits in L1 and the parallel sweep is unbeatable — no GPU implementation can compete because the workload doesn't have enough work for the dispatch overhead to amortize. The hybrid dispatcher correctly routes this to radix-opt rather than slot-lock (which would lock-contend), but radix-opt still loses to L1-cached hash. This is the floor.
The synthetic scale sweep tells the same story for non-TPC-H workloads. 1B rows × 1M unique groups — the workload that v0.1.2 lost 1.5× on — now wins 3.2× over DuckDB 16-thread (770 ms vs 2500 ms). The hybrid dispatcher routes this one to radix-opt because 1M groups at 1B rows pushes the slot-lock load factor toward 1.0; radix's amortized work-per-row stays bounded. 500M × 1M groups: 3.4× win (242 ms vs 820 ms). 100M × 1M groups: 2.6× win (47 ms vs 124 ms). These are the workload sizes that real production dashboards and ETL jobs hit; that's where the win matters.
Multi-aggregate fusion — where the wins compound
One read, four aggregates. Every existing analytical engine treats SELECT SUM(x), MIN(x), MAX(x), COUNT(x) FROM t as four passes of the column. gpudb fuses them into a single Metal kernel that reads each value once and updates four registers in parallel. Against DuckDB CPU 16-thread on TPC-H SF10 lineitem, the speedup ratio depends on which column you fuse — and it's 9–25×:
Three readings of that chart:
- The Metal fused kernel finishes in ~1.1 ms regardless of column. Sum + min + max + count over 60 million 64-bit values, in a single GPU pass, in roughly the time it takes to dispatch the kernel. The throughput is ~475 GiB/s = 87% of M4 Max LPDDR5X peak. There is no software optimization left for this kernel.
- The DuckDB CPU number ranges 11–27 ms. The variance is not random — it's about how friendly the data layout is to CPU vectorization.
l_orderkeyat 15M unique values is the friendliest case (3 ms hash, 11 ms total);l_quantityat 50 unique is the least friendly (the loop overhead dominates the work). The Metal kernel doesn't care: it pays the same ~1 ms regardless of distribution. - The 25.5× headline is real, the 9.7× floor is also real. Honest GPU databases tell you both. The user gets at least 9.7× on TPC-H SF10 multi-agg fusion no matter which column they fuse, and 25.5× on the workloads where DuckDB's CPU vectorizer is having a hard day.
How close are we to the silicon ceiling?
The right way to read all the numbers above is not "Metal is faster than CPU" — it's "Metal is faster than CPU because it's running close to what the silicon physically allows." The same lens applied to CUDA tells the second half of the story: the RTX 4090 has a much higher absolute memory-bandwidth ceiling than the M4 Max, but the gpudb CUDA kernels currently leave more headroom against that ceiling. Here's the picture across four representative kernels:
The chart is the strongest counter to the most common GPU-database claim: "we're 50× faster than CPU." That claim is usually true for one cherry-picked workload and unreplicable on the next. The honest version is what's in this chart: gpudb's streaming-aggregate kernels (SUM, multi-agg fusion) are within striking distance of the hardware ceiling on Metal — they sit at 85–87% of LPDDR5X. There is almost no software performance left to squeeze. The CUDA kernel hits a higher absolute throughput (525 GiB/s vs Metal's 464) but leaves 48% of GDDR6X on the table, which is one of the next clear optimization targets. The radix-sort GROUP BY kernel runs at 35% of its ceiling because sort is structurally more compute-bound per byte than streaming reduction — that's not a software gap, it's the algorithm.
The CUDA backend earns its keep on workloads where the 4090's higher absolute ceiling matters: resident-column SUM hits 1187 GiB/s on a 100M int64 column because the data never leaves VRAM and the 4090's pure memory bandwidth dominates. The CUDA hash-join probe ships in v0.1: at 1M build × 10M probe with 97% selectivity, CUDA wins 3.7× wall and 107× kernel against single-thread CPU. Joins typically dominate analytical query time, so this is the operator that makes the CUDA backend matter for real workloads — not the SUM kernel, which CPU handles fine when the data is small. The Metal hash-join is currently a CPU-fallback scaffold; getting Metal to the same operator parity as CUDA (on the workloads where it can win) is the v0.2 line.
Where it loses, and why
The most attacked benchmark for any GPU database is show me the workload where you lose. v0.1.3 has exactly two, and both are structural — they reflect physics, not missing engineering.
1. Very low cardinality on small-to-mid inputs. TPC-H SF10 GROUP BY on l_quantity (50 unique values across 60M rows) loses to DuckDB CPU 14×. The CPU's hash table fits in L1 and the parallel sweep amortizes perfectly; no GPU implementation can compete because the workload doesn't have enough distinct work per dispatch to overcome setup cost. The hybrid dispatcher routes this case to radix-opt rather than slot-lock (slot-lock would lock-contend on the 50-slot working set), but radix-opt still loses to L1-cached CPU hash. This is the floor and won't be fixed — anyone shipping this benchmark in their headline numbers and claiming a GPU win is either using a tiny CPU baseline or misreporting.
2. Synthetic 1B × 1K groups (low cardinality, billion rows). 150 ms DuckDB vs 637 ms Metal — CPU wins 4.2×. Same cause as above scaled up: at 1K unique groups across 1B rows, the CPU hash table is cache-resident the entire run, and DuckDB's 16-thread reduction-tree sweeps the column at memory bandwidth. The slot-lock GPU path would chronically lock-contend on the same 1K hot slots; the radix path pays for sort work the CPU doesn't need. This is what the hybrid dispatcher's planner would route to CPU in production deployment — and that's exactly the right call.
Where v0.1.3 used to lose but no longer does:
- SF10 GROUP BY
l_orderkey(15M unique) — was 1.78× slower in v0.1.2; now 1.30× faster (slot-lock 32K). - Synthetic 1B × 1M groups — was 1.5× slower (host-side segment-reduce bottleneck on radix sort); now 3.2× faster (radix-opt path with vectorized 4× ulong4 loads + simdgroup-prefix-sum scan).
The remaining honest caveat for the SQL-extension-via-DuckDB path: when gpu_sum is called on a parquet column the GPU hasn't materialized yet, the extension currently uploads the column from Arrow into a fresh MTLBuffer per query. At SF1 (46 MiB) the upload dominates; at SF10 (458 MiB) more so. The fix is MTLBuffer caching per parquet column at the extension layer so the second call against the same column hits resident data — one to two days of work in src/extension/gpu_sum_extension.cpp. The libgpudb hybrid planner already knows to route the cold-first call to CPU; the extension just needs to consult it.
The hybrid planner — and the two Metal GROUP BY paths
The throughline of the "where it loses" section is the same as it was in v0.1.2: the GPU isn't always the right answer, and a database that uses the GPU when the CPU is faster is slower in the average case and only faster on cherry-picked microbenches. The whole point of a hybrid CPU/GPU planner is to consult the workload at dispatch time and make the right call. v0.1.3 raises the bar by giving the planner two GPU paths to choose between, not just one:
| Operator | Path | Algorithm | Sweet spot |
|---|---|---|---|
| SUM | Metal streaming | tile-parallel reduction, simdgroup_sum, threadgroup memory accum | Always (UMA = zero transfer; ~85% of LPDDR5X peak) |
| SUM (CUDA) | Discrete GPU | same algorithm shape; 525 GiB/s on RTX 4090 GDDR6X | Resident-column path (cold loses 6–9× to CPU on PCIe) |
| Multi-agg fusion | Metal fused | 4 reductions in one pass; ~475 GiB/s = 87% of LPDDR5X | Always (compounds the SUM win 4×) |
| GROUP BY | Metal slot-lock 32K | 32,768 hash partitions × 1024 threadgroup-mem slots; 32-bit CAS protecting 64-bit sum | 1024 ≤ unique ≤ 16M (the production OLAP regime) |
| GROUP BY | Metal radix-opt | vectorized 4× ulong4 loads, in-block 8-bit multi-split scatter via simdgroup prefix-sum, simdgroup-prefix-sum bucket scan | < 1024 unique or > 16M unique (lock-contention or slot overflow regimes) |
| GROUP BY (CUDA) | Open-addressing hash | atomicCAS-protected 64-bit slots; ~520 GiB/s on RTX 4090 | Cardinality > ~10K |
| GROUP BY (CPU) | L1-cached parallel hash | cache-resident at low cardinality, unbeatable | ≤ 1K unique with cache-resident input |
The key v0.1.3 insight: 32,768 hash partitions × 1024 slots = 33.5M slot capacity. At TPC-H SF10's 15M unique l_orderkey values, that's a load factor of ~0.45 — the per-partition Poisson tail rarely exceeds 543 occupied slots, well within the 1024-slot working set. Earlier 4K-partition variants saturated around 3M unique (per-partition mean ~750, tails clipping); doubling partitions twice to 32K is what unlocked the 1.30× win. Algorithmic decisions like this are why the slot-lock kernel exists at all.
Per-call dispatch goes through src/operators/planner.cpp and emits a one-line reason code (SlotLock_SweetSpot, RadixOpt_HighCardinality, Cold_BelowGpuBreakeven, etc.) so the user can always see why a query was routed where. The user can also override per-call via GPUDB_FORCE_BACKEND=cpu|cuda|metal and GPUDB_METAL_GROUPBY_PATH=slotlock|radix. That's not just debugging — it's the contract that makes "the GPU one will be faster" defensible instead of hopeful.
Why DuckDB, not a new database
The single most important architectural choice in the project is what gpudb is not. It is not a fork of DuckDB. It is not a new analytical engine that mimics DuckDB's SQL surface. It is a thin DuckDB extension that registers a handful of aggregate functions and then defers everything else — parsing, planning, optimization, plan-tree execution, parquet reading, type checking, NULL semantics, joins, projections — to the host DuckDB process.
This sounds like a small choice. It is the entire reason the project has a path to users.
If gpudb were a new database, the install instruction would be: download a binary, set up a server, configure storage, migrate your data, rewrite your queries to point at it, deal with the breakage. That is the path every dead GPU database walked, and the install instructions for none of them survive the friction of "and now do it on your laptop right now."
Because gpudb is an extension, the install path is one DuckDB LOAD away. (The community-extensions submission is in flight; once it lands, it'll be INSTALL gpudb FROM community; LOAD gpudb; and you're done.) Your existing queries become faster on the columns you opt into. Your storage doesn't change. Your tooling doesn't change. The DuckDB community already grew the user base; gpudb just rides it.
That same logic is why I'm not adding a DSL, not building a new SQL dialect, not asking anyone to learn anything new. Every aggregate function I ship has to look exactly like the DuckDB function it replaces, just with gpu_ in front. If a user has to think to use it, the project loses.
The Apple Silicon angle
The Apple Silicon backend is the part of gpudb that I think will actually drive adoption — not because Apple Silicon is faster than a 4090, but because the laptop you're reading this on probably has it.
The unified-memory architecture (UMA) of M-series Macs gives you something no PCIe-attached discrete GPU can: zero-copy access to system RAM from the GPU. There is no host-to-device transfer. The same 64 GiB of LPDDR5X you allocated for your Python notebook is directly addressable from a Metal compute kernel. For analytical workloads, where so much of the cost on traditional GPU databases is the PCIe transfer, this is structural. We aren't paying it.
That's why Metal can win on cold data at scale, where CUDA on a 4090 with PCIe 4.0 has to spend 80 ms transferring a column before it can touch it. At TPC-H SF10 cold, Metal beats single-threaded CPU 2.12× — winning even when the data hasn't been touched yet — because there is no transfer to amortize.
The other half of the Apple Silicon story is bandwidth: an M4 Max chip has ~546 GB/s of LPDDR5X. That's the same bandwidth ceiling as a mid-range desktop GPU from a few generations ago. When you write Metal kernels that hit it (and gpudb does — 87% of peak on multi-aggregate fusion) you are doing real GPU computation on a laptop, with no fan ramp, on battery, at 30 watts.
That has nothing to do with whether Apple Silicon is "better than NVIDIA." It isn't, on the workloads that pin the 4090's 1008 GB/s. It has everything to do with the fact that the device sitting on every working developer's desk in 2026 has a real GPU on it, and no published SQL engine had wired into it. That is an empty field, and emptiness is the whole game in early-stage open source.
The architecture, in one diagram
The dispatch decision happens at runtime, per-call, based on a small set of empirical thresholds derived from BENCHMARK.md: input size, cardinality, residency, transfer overhead, hardware class. The thresholds aren't perfect yet — that's the open research problem from Rosenfeld & Breß's CSUR 2022 survey and Cao's VLDB 2024 paper — but we have a defensible starting set, and they're tunable per workload.
What's next
Three priorities, in order:
1. DuckDB Community Extensions merge. The .duckdb_extension binary now ships with the official metadata footer, and the YAML + extension submission is open at duckdb/community-extensions PR #1898. Once that merges, install becomes INSTALL gpudb FROM community; LOAD gpudb; from any DuckDB CLI on any machine — no wget, no -unsigned flag, just one line. Highest-leverage action for distribution.
2. MTLBuffer caching in the SQL extension. Eliminates the per-query upload cost on Metal so the second call against the same parquet column hits resident data — the regime where the kernel hits 87% of LPDDR5X. The libgpudb hybrid planner already detects this; the extension layer just needs to call it. One to two days in src/extension/gpu_sum_extension.cpp.
3. Real Metal hash-join sort-merge. Currently a CPU-fallback scaffold (CUDA hash-join already ships at 3.7× wall / 107× kernel over CPU on 1M-build × 10M-probe). Joins typically dominate analytical query time, so this is the operator that brings the Metal backend to full TPC-H operator parity with CUDA.
After that, the operator backlog is well-defined: gpu_cache(table, col) table function for explicit resident-column control; GPU window functions as proper operators (the gap Sirius's CIDR 2026 paper explicitly lacks); and string/regex operators where libcudf-class functionality on Metal doesn't exist anywhere yet.
What just shipped — and won't be on the next-steps list — is the entire v0.1.3 hybrid GROUP BY work that took TPC-H from a 1.78× loss to a 1.30× win on the canonical workload, and the prebuilt-extension binary path that took install from "clone + cmake + 5 minutes" to curl + duckdb -c "LOAD ..." in 15 seconds. That's the line between the v0.1 and v0.2 cycles.
Try it
The repo is at github.com/singhpratech/duckdbgpumetaldbram. Apache-2.0. As of v0.1.3 (shipped tonight, with the hybrid Metal GROUP BY), no source build is required on Apple Silicon — download the prebuilt extension and load it in any DuckDB v1.5.x:
# macOS (Apple Silicon, Metal — v0.1.3)
curl -fL -o gpudb.osx_arm64.duckdb_extension \
https://github.com/singhpratech/duckdbgpumetaldbram/releases/download/v0.1.3/gpudb.osx_arm64.duckdb_extension
duckdb -unsigned -c "LOAD './gpudb.osx_arm64.duckdb_extension'; \
SELECT gpu_sum(range::BIGINT) FROM range(100000000);"
# -> backend=METAL · 499999500000
# Linux (CUDA — use v0.1.2 binary; CUDA backend unchanged in v0.1.3)
wget https://github.com/singhpratech/duckdbgpumetaldbram/releases/download/v0.1.2/gpudb.linux_amd64.duckdb_extension
duckdb -unsigned -c "LOAD './gpudb.linux_amd64.duckdb_extension'; \
SELECT gpu_sum(range::BIGINT) FROM range(100000000);"
# -> backend=CUDA
The -unsigned flag is needed because the extension hasn't joined the signed community-extensions index yet — that's what PR #1898 unlocks. Once it merges, install becomes INSTALL gpudb FROM community; LOAD gpudb; with no flags and no manual download.
If you'd rather build from source — or you're on a platform without a prebuilt binary — the README ships a build.sh script that auto-detects CUDA on Linux, Metal on macOS, and falls back to CPU-only otherwise. Five CLI tools land alongside the loadable extension: gpudb-sql (DuckDB-embedded SQL CLI), gpudb-bench, gpudb-groupby-bench, gpudb-window-bench, gpudb-hashjoin-bench. The reproduction commands for every number in this post live in BENCHMARK.md.
Issues, ideas, benchmarks from your hardware: the GitHub issue tracker is the right place. The build process, design tradeoffs, and ongoing benchmarks will keep landing here on theaivibe.org as the v0.2 work goes in.
Where you can help most right now
Every benchmark in this post came from one of two machines: an RTX 4090 Laptop and an Apple M4 Max. The hybrid planner thresholds in v0.1.2 are derived from those two boxes. The single highest-leverage contribution someone reading this can make is to run the install commands above, point gpudb at a real query you already run — a daily ETL aggregate, a dashboard GROUP BY, a parquet column you've sized — and file an issue with what you measured: hardware, row count, unique-group cardinality, wall time on CPU vs gpu_sum. The planner thresholds get sharper with every datapoint on hardware nobody at the project has tested yet — M1 Pro, M2 Ultra, M3 Max, RTX 3090, RTX 5090, Tesla, anything not in the matrix. That's the contribution that most directly improves what every other gpudb user gets when they LOAD the extension tomorrow.
Beyond benchmarks: the public roadmap is in the README and in this post; if any of those line items (Metal hash-join, GPU-resident segment-reduce, MTLBuffer caching, GPU window functions, string operators) is interesting to you, the contributor lane is wide open and the codebase is small enough to read end-to-end in an afternoon. good-first-issue labels are landing on the issue tracker as the v0.2 backlog gets sliced.
The right way to read the GPU-database graveyard isn't "GPU databases failed." It's "GPU databases that asked users to migrate failed." gpudb doesn't ask anyone to migrate; it just makes the SUM and the GROUP BY and the multi-aggregate fusion faster on the workloads where the silicon affords it. That's the bet — and now, with v0.1.3 and a prebuilt extension binary, it's a bet you can verify on your own M4 against your own data in the next thirty seconds.
Subscribe to new posts from theaivibe.org
Related Posts
The First SQL Engine for Apple Silicon GPUs Is Now a DuckDB Community Extension
In May 2026 I shipped gpudb v0.1 — the first SQL execution engine targeting Apple Silicon GPUs, built as a DuckDB extension with a CUDA backend on Linux. Three releases later, the project crossed two lines at once. v0.3.0's streaming-aggregate rewrite reached parity with native DuckDB on end-to-end TPC-H queries — the worst cell improved roughly 100×, from 11.05 s to 0.109 s. And gpudb became an official DuckDB Community Extension: INSTALL gpudb FROM community now works in any DuckDB ≥ 1.5.5, signed, no flags. This is the full arc — what v0.1 proved, what v0.2 honestly lost, what v0.3 fixed, and why the next GPU frontier is joins.

The Agent-Written Data Pipeline: The Review Bottleneck Nobody Priced In
AI agents can now write dbt models, SQL transforms, and backfills that pass CI and ship. The catch: a wrong number doesn't crash, it quietly poisons every dashboard downstream. The hard part moved from authoring to verification.

We Published Our 110× Loss. One Release Later, It Was Gone.
A reviewer on gpudb's DuckDB community-extensions PR asked the question every GPU project dreads: forget the kernel benchmarks — what does a user actually see end-to-end? We ran it honestly. Native DuckDB won every query shape, by 3× to 109×, against our own extension. We published those numbers in our own release notes — and the act of writing them down produced the structural diagnosis that closed the entire gap in the very next release. The fix was the opposite of what a GPU database is supposed to do: delete the GPU from the hot path. This is the full story, with every number.