Skip to content
Back to Blog

DuckDB GPU Acceleration After 2.0: Where It Wins, Where It Doesn't, and Why the Mac Is Different

Prateek SinghOctober 30, 202614 min read45 views
DuckDB GPU Acceleration After 2.0: Where It Wins, Where It Doesn't, and Why the Mac Is Different

DuckDB 2.0 turned DuckDB into a server, AWS is buying the company behind it, and the GPU question is back. DuckDB 2.0 also changed the query format my GPU extension reads, and gpudb 0.7.1 adapts to it: on an Apple M4 Max, 18 of 22 TPC-H SF10 queries run on the GPU under DuckDB 2.0, 3.7x faster end to end, with every answer identical to DuckDB's. Here is where a GPU helps DuckDB, how gpudb decides, and why the Mac is the interesting case.

DuckDB 2.0 shipped on 21 October as a long-term support release, and the headline feature is that DuckDB can now be a server. Any DuckDB process can serve its databases over the new Quack protocol, and a new CONNECT statement can switch to a remote database, including PostgreSQL and MySQL. Two months earlier, AWS announced it is acquiring DuckLabs, the company behind DuckDB; DuckDB itself stays MIT-licensed under the non-profit DuckDB Foundation. The in-process database that people ran on laptops is turning into shared infrastructure, and that brings an old question back: should that shared box be using its GPU?

I have a stake in the answer. gpudb is my Apache-2.0 extension that runs plain DuckDB SQL on the GPU, Apple silicon through Metal first and NVIDIA through CUDA. DuckDB 2.0 changed the query format gpudb reads, and version 0.7.1 adapts to it. This post covers where a GPU helps DuckDB, what changed in 2.0, and what the numbers look like now.

gpudb 0.7.1 on DuckDB 2.0
18 of 22
TPC-H SF10 queries on the Mac's GPU, up to 33.9x faster
All 22 queries, end to end
3.7x
993 ms on plain DuckDB 2.0, 266 ms with gpudb
Rows different from DuckDB
0
on every query, on both DuckDB versions
Code to change
1 line
gpudb.connect() in place of duckdb.connect(); your SQL stays the same

Measured 29 October 2026 with the released wheels from PyPI: TPC-H SF10 (60,000,000-row lineitem), an Apple M4 Max with 64 GB of unified memory, warm, execute() path, minimum of 5 runs, every row compared with native before any time was counted.

DuckDB stopped being a laptop tool

The adoption numbers are the reason any of this matters. At DuckCon in June 2026, the DuckDB team put downloads at about 40 million a month, up from about 4 million in August 2024. In the 2025 Stack Overflow developer survey, DuckDB's usage rose from 1.4% to 3.3% of respondents, and it ranked fourth for admiration, 0.2 points behind SQLite. A year ago its own estimate was 20 or more Fortune 100 companies using it.

2.0 is built for that audience. Besides the server mode, it adds asynchronous I/O for Parquet and CSV, a first-class VARIANT type and a new SQL parser that extensions can hook into, and it is supported until at least October 2027. It also got faster at the plain work: on my TPC-H SF10 runs below, the 22 queries take 993 ms on DuckDB 2.0.0 against 1,236 ms on 1.5.6, about 20% less, without changing a line of SQL. Any GPU story about DuckDB has to beat that moving target.

Where a GPU helps DuckDB, and how gpudb decides

A GPU shines at doing the same arithmetic over millions of values that are already in memory. The skill is sending it exactly those queries and nothing else, and that is what gpudb is built around.

  • Storage-bound queries stay storage-bound. If a query spends its time pulling Parquet from S3 or a disk, the network or the drive sets the pace, for a CPU and a GPU alike. The GPU's work starts once the data is in memory.
  • It warms up in the background. The first time you query a table, DuckDB answers immediately while gpudb copies the columns it needs into GPU memory. Once they are in place, the GPU takes over. You never wait on the copy.
  • It leaves DuckDB's fastest cases to DuckDB. Some work is already fast on the CPU: a GROUP BY over a few hundred distinct values fits in the CPU's fastest cache, and DuckDB answers it in milliseconds. gpudb hands those straight to DuckDB, along with single-table GROUP BYs under 1,000 groups, window functions and sums over DOUBLE, so they run at full DuckDB speed.
  • It takes the heavy lifting. Large GROUP BYs, joins feeding an aggregation, and top-k queries over tens of millions of rows in memory run on the GPU. That is most of TPC-H, and it is where the 1.7x to 33.9x speed-ups below come from.

And then there is the copy. On a PC or a server, the GPU is a separate card with its own memory, and data reaches it over the PCIe bus, a cost every NVIDIA-based engine has to work around. On Apple silicon, the CPU and GPU sit on the same chip and share one pool of memory. gpudb still builds GPU-resident copies of the columns it uses, but no bus sits between the data and the GPU. That is the main reason the Mac is a different case, and why I started there.

What changed in DuckDB 2.0

On DuckDB 2.0.0, the released gpudb 0.7.0 installed from the community registry and loaded fine, with its Metal backend registered. Then I ran TPC-H at SF10 through its plain-SQL path, and 0 of 22 queries reached the GPU. Every one came back marked "shape": not a form the rewrite recognised, so DuckDB answered it. The same wheel on DuckDB 1.5.6, on the same data the same evening, put 19 of 22 on the GPU.

The cause was one layer down. DuckDB's stable C API has no hook that sees a statement before it is planned, so gpudb's Python wrapper reads each statement through DuckDB's own parser, json_serialize_sql, and decides from that tree whether there is a GPU rewrite. DuckDB 2.0 changed the shape of that tree:

-- the same statement, serialized by DuckDB's own parser
SELECT json_serialize_sql('SELECT k, sum(v) FROM t GROUP BY k');

-- DuckDB 1.5.6: sum's input is a child
"function_name": "sum",
"children": [ { "class": "COLUMN_REF", "column_names": ["v"] } ]

-- DuckDB 2.0.0: sum's input moved, and gained a wrapper
"function_name": "sum",
"arguments": [ { "name": "",
                 "expression": { "class": "COLUMN_REF", ... } } ]

Function inputs moved from children into arguments, each wrapped as a name and an expression. Constants changed format too, and a few nodes gained fields. gpudb's matcher saw sum() with no input, decided it did not understand the statement, and handed it to DuckDB. Until 0.7.1 shipped on 29 October, a fresh install paired with DuckDB 2.0 ran every query on DuckDB's CPU engine.

What did not happen matters most. Not one answer was wrong, and nothing was slower than DuckDB alone, because of the two rules gpudb is built on: never a different answer from DuckDB, and never slower than DuckDB. When the wrapper does not understand a statement, it does not guess. It hands the statement to DuckDB and DuckDB answers it. A format change that could have produced wrong results produced a missing speed-up instead.

The fix in 0.7.1

DuckDB 2.0 still accepts a tree in the old 1.5 shape when it turns one back into SQL. So the fix converts a 2.0 tree to the 1.5 shape once, at the single place the wrapper serialises a statement, and every reader downstream, including the C++ rewrite function in the extension, works unchanged. That made it a Python-only release: duckdb-gpudb 0.7.1 on PyPI, with the extension binary unchanged at 0.7.0, so nothing in DuckDB's community registry had to be rebuilt. It ships with 115 fixtures taken from DuckDB 1.5.6, covering the 22 TPC-H queries and one statement for each shape the rewrite supports, and the tests run on both versions.

One shape now stays on DuckDB on 2.0 on purpose: avg over DECIMAL. DuckDB 2.0's optimizer finalises that average in one of two ways depending on the plan, and the two can differ in the last bit. Under the never-different rule the wrapper declines rather than guess which one DuckDB will choose, which is why TPC-H Q1, a GPU query on 1.5.6 at 7.5x, stays on DuckDB 2.0.

The numbers on DuckDB 2.0

All measured on 29 October with the released wheels from PyPI, in clean environments, on an Apple M4 Max with 64 GB of unified memory: TPC-H scale factor 10, a 60-million-row lineitem, the minimum of five warm runs per query, every row compared with DuckDB's own answer before any time was counted.

TPC-H SF10 on DuckDB 2.0.0, Apple M4 Max (Metal): DuckDB ms ÷ gpudb 0.7.1 ms log scale · execute() path · 29 Oct 2026 · every row identical to DuckDB · dashed = stays on DuckDB 0.5x1x2x5x10x20x50x Q1stays on DuckDB: avg over DECIMAL, declined on DuckDB 2 · 55.9 ms Q2stays on DuckDB: correlated subquery · 8.8 ms Q33.71x32.5 → 8.8 ms Q410.68x30.7 → 2.9 ms Q533.85x36.7 → 1.1 ms Q61.94x11.9 → 6.1 ms Q73.35x32.9 → 9.8 ms Q84.58x37.9 → 8.3 ms Q919.92x128.4 → 6.4 ms Q107.25x68.5 → 9.5 ms Q110.98x6.3 → 6.5 ms Q123.50x36.8 → 10.5 ms Q1311.31x135.2 → 12 ms Q146.39x30.7 → 4.8 ms Q151.73x20.5 → 11.8 ms Q16stays on DuckDB: below the measured threshold · 35.7 ms Q178.75x29.8 → 3.4 ms Q1818.66x99.4 → 5.3 ms Q197.10x31.6 → 4.5 ms Q20stays on DuckDB: correlated subquery · 32.5 ms Q213.57x71.3 → 19.9 ms Q2219.26x19 → 1 ms
All 22 TPC-H queries at SF10 on DuckDB 2.0.0 with gpudb 0.7.1, Apple M4 Max: DuckDB's time divided by gpudb's, log scale, both times beside each bar. Four queries stay on DuckDB: Q2 and Q20 have correlated subqueries that do not bind on their own, Q16 is below the measured threshold, and Q1 is the avg-over-DECIMAL decline described above.

18 of 22 queries run on the GPU, and 0 rows differ from DuckDB on any of them. Seventeen are between 1.73x (Q15) and 33.9x (Q5) faster. The eighteenth is Q11, at 0.98x: a 6-millisecond query that sits right on the line. The gpudb project's own run of the same release measured it at 1.04x, and on 1.5.6 I measured 1.02x. This is exactly the case gpudb's per-process check is for. After a statement's first few GPU runs, the wrapper times DuckDB's own plan once on a side cursor, and hands the statement back to DuckDB if the GPU was not faster. Across all 22 queries, DuckDB 2.0 alone takes 993 ms and with gpudb takes 266 ms, 3.7x end to end.

DuckDBgpudbon the GPUrows differentspeed-up on those queriesall 22, DuckDB aloneall 22, with gpudb
1.5.60.7.019 of 2201.10x to 41.7xnot totallednot totalled
2.0.00.7.00 of 220none977 ms977 ms
1.5.60.7.119 of 2201.02x to 23.1x1,236 ms229 ms
2.0.00.7.118 of 2200.98x to 33.9x993 ms266 ms
TPC-H SF10 on an Apple M4 Max, plain-SQL path, minimum of 5 runs. The 0.7.0 rows were measured on 28 October and the 0.7.1 rows on 29 October; the 2.0.0 + 0.7.0 total is DuckDB alone, because nothing reached the GPU. Range end-points on 0.7.1: Q11 and Q18 on 1.5.6, Q11 and Q5 on 2.0.0.

DuckDB 2.0 is also a faster baseline, which makes the result stronger. Q1 takes 108 ms on DuckDB 1.5.6 and 56 ms on 2.0, Q19 63 ms and 32 ms, Q21 137 ms and 71 ms. Even against that quicker CPU engine, gpudb takes the whole suite from 993 ms to 266 ms, and the biggest wins are the same queries as on 1.5.6: Q5, Q9, Q18 and Q22, joins and large GROUP BYs over data in memory.

These numbers are the Apple silicon side. The fix sits in the shared Python layer, so it applies to the NVIDIA CUDA backend as well; I have not published CUDA numbers on DuckDB 2.0 yet.

gpudb and DuckDB 2.0's server mode

The new server mode is the most interesting part of DuckDB 2.0 for a GPU. gpudb's plain-SQL rewrite lives in the client wrapper, in Python, because DuckDB's C API offers no hook before planning, so a statement that reaches a DuckDB server over the network goes straight to the server's planner. A server that has loaded the extension still offers the explicit gpu_* functions to its clients; the automatic routing is a Python-client feature today. I have not yet tested gpudb behind duckdb -serve.

Who else is putting DuckDB on a GPU

The serious other project is Sirius, from the University of Wisconsin-Madison with support from NVIDIA engineers. It is a DuckDB extension that takes DuckDB's query plan and runs it on the GPU with NVIDIA's cuDF libraries, and in December 2025 NVIDIA published a ClickBench record for it on a GH200 instance. The team's CIDR 2026 paper reports large cost-efficiency gains on TPC-H and ClickBench. It is strong work with NVIDIA's libraries behind it.

Siriusgpudb
Who builds itUniversity of Wisconsin-Madison, with support from NVIDIA engineersan independent open-source project, with outside contributors
GPUsNVIDIA, through cuDF and RMMApple silicon through Metal, and NVIDIA through CUDA
How it plugs into DuckDBan extension that takes DuckDB's plan as Substrait and runs the operators on the GPUan extension on DuckDB's stable C API, plus a Python wrapper that rewrites plain SQL before DuckDB plans it
What decides GPU or CPUwhether it supports the operatorsbounds measured before a release, then a timing against DuckDB in your own process
Headline resulta ClickBench record on a GH200 at $1.5 an hour, at least 7.2x the cost efficiency of the top CPU systems (NVIDIA, December 2025)18 of 22 TPC-H SF10 queries on an M4 Max on DuckDB 2.0, 0 rows different (this post)
How you get itbuild from source; no tagged release, not in DuckDB's community registrypip install duckdb-gpudb, or INSTALL gpudb FROM community for the explicit functions
LicenceApache-2.0Apache-2.0
From each project's own published material, checked 29 October 2026: the NVIDIA Technical Blog and CIDR 2026 paper for Sirius, the Sirius repository and DuckDB's community registry; the gpudb repository and PyPI for gpudb. Sirius's result is ClickBench on NVIDIA hardware and gpudb's is TPC-H on Apple silicon: they are different benchmarks on different machines and do not compare directly.

On NVIDIA data-center hardware, Sirius has the most momentum. gpudb's distinctive ground is the Mac: Sirius, cuDF and HeavyDB all need an NVIDIA GPU, so on Apple silicon gpudb is the GPU option for DuckDB, and it runs on NVIDIA through CUDA as well.

So, is it a game changer?

For DuckDB on a Mac, yes. If you do analytics with DuckDB on Apple silicon, with data that fits in memory, there is a GPU on the same chip sitting idle while DuckDB works. One pip install puts it to work on the heavy queries: 18 of 22 TPC-H queries on the GPU and 3.7x end to end on DuckDB 2.0, with every answer identical to DuckDB's and everything else handed back to DuckDB at full speed.

Across DuckDB as a whole, the gains go where the work is in memory. Queries that spend their time reading files from S3 or disk are paced by storage, and the automatic path runs from Python rather than through 2.0's server mode. Those are the places with the most room to grow.

The DuckDB 2.0 episode is the best evidence I have that the design holds. A change in DuckDB's query format made the GPU disappear, the cost was speed and never a wrong answer, and a fix at a single point brought it back.

Try it

pip install -U duckdb-gpudb        # 0.7.1: works with DuckDB 2.0 and 1.x
gpudb my.duckdb                    # the footer says where each ran

# Python: the same surface as duckdb.connect()
import gpudb
con = gpudb.connect("my.duckdb")
con.execute("SELECT ... GROUP BY ...").fetchall()
print(con.last_rewrite()["detail"])   # where it ran, and why

gpudb runs on Apple silicon Macs on macOS 15 or later, and on x86-64 Linux with an NVIDIA driver. From any DuckDB client, INSTALL gpudb FROM community; LOAD gpudb; gives you the explicit gpu_* functions; I checked that this loads on DuckDB 2.0.0. The install matrix is in the install guide, the design behind the two rules is in TRANSPARENT_DESIGN.md, and what stays on DuckDB, reason by reason, is in KNOWN_ISSUES.md. If you run it on a different Mac or GPU, the numbers from your machine are new information, and I would like to see them as an issue on GitHub.

FAQ: DuckDB and the GPU after 2.0

Does DuckDB 2.0 support GPU acceleration?

Not by itself: DuckDB 2.0 has no GPU backend. GPU acceleration comes from extensions. gpudb 0.7.1 runs plain DuckDB SQL on Apple silicon GPUs through Metal and on NVIDIA GPUs through CUDA, and works with DuckDB 2.0. Sirius, from the University of Wisconsin-Madison with NVIDIA, runs DuckDB plans on NVIDIA GPUs.

Does gpudb work with DuckDB 2.0?

Yes, from version 0.7.1. Version 0.7.0 loaded on DuckDB 2.0 but sent every statement back to DuckDB, because DuckDB 2.0 changed the shape of the query tree that json_serialize_sql produces. 0.7.1 converts the new shape back to the old one at a single point. On an Apple M4 Max at TPC-H scale factor 10, 18 of 22 queries run on the GPU with zero rows different from DuckDB.

Can DuckDB use the GPU on a Mac?

With gpudb, yes. It is an Apache-2.0 DuckDB extension with a Metal backend for Apple silicon Macs on macOS 15 or later, installed with pip install duckdb-gpudb. Sirius, cuDF and HeavyDB all require an NVIDIA GPU.

Will a GPU speed up DuckDB queries on Parquet files in S3?

The download and decode are paced by the network and the disk, for a CPU and a GPU alike. Once the data is in memory, the heavy work speeds up: large GROUP BYs, joins with aggregation, top-k. The first query over a table runs on DuckDB while gpudb copies the columns it needs to the GPU, so you never wait on the copy.

gpudb vs Sirius: which should I use?

Sirius targets NVIDIA data-center GPUs, with NVIDIA's libraries and a ClickBench record behind it. On a Mac, gpudb is the GPU option for DuckDB, because Sirius requires NVIDIA hardware. gpudb also runs on NVIDIA GPUs through CUDA.

References & Citations

Subscribe to new posts from theaivibe.org

No spam — just new posts. One-click unsubscribe.
Share this article

Related Posts