All publications
preprint2026| theaivibe.org / GitHub

gpudb: A GPU-Resident Execution Engine for DuckDB on NVIDIA CUDA and Apple Silicon Metal

Prateek Singh

gpudb: A GPU-Resident Execution Engine for DuckDB on NVIDIA CUDA and Apple Silicon Metal

Abstract

We present gpudb, a DuckDB extension that adds GPU-resident analytical operators with two production backends — NVIDIA CUDA and Apple Silicon Metal — sharing one C++ codebase. To our knowledge it is the first published SQL execution engine that targets Apple GPUs. We describe the four-layer architecture, the CUDA and Metal kernel designs (including a multi-aggregate fusion pass that reaches 87% of LPDDR5X peak bandwidth on M4 Max), a cardinality-aware hybrid CPU/GPU planner that beats both pure-CPU and pure-GPU dispatch at the 1M-unique-key crossover, and an algorithm playbook for SQL window functions that the strongest competing GPU OLAP engine (Sirius, CIDR 2026) does not ship. On TPC-H SF10 lineitem queries, multi-aggregate fusion delivers 22-25× speedups over a 16-thread DuckDB CPU baseline, hash-join probe at 97% selectivity runs 3.7× wall-clock and 107× kernel-only on CUDA, and GROUP BY at 50M-1B rows × 1M unique keys is 3-9× faster on M4 Max. We discuss the GPU-database commercial graveyard of 2013-2024 (HEAVY.AI, BlazingSQL, Voltron Data, Brytlyt, Kinetica), the architectural openings this leaves, and why a DuckDB-extension shape — not a new database — is the only commercially viable position for a 2026 GPU SQL project.

1. Introduction

Analytical SQL on GPUs has been a research direction for over a decade and a commercial direction for almost as long. The commercial result has been bleak. By the end of 2025, every standalone GPU-database vendor that launched between 2013 and 2020 had either been acqui-hired, pivoted, or gone dormant: HEAVY.AI (formerly MapD/OmniSci) was absorbed by NVIDIA in 2025; BlazingSQL's last release was version 0.21 in April 2021; Voltron Data laid off roughly half its staff in September 2024; Brytlyt was acquired by Opensignal in June 2024; Kinetica pivoted to real-time vector similarity search for GenAI. The pattern is consistent enough to be a thesis: shipping another GPU SQL engine in 2026 is not a viable bet.

And yet the engineering substrate has never been better. The RTX 4090 ships with roughly 1 TB/s of GDDR6X bandwidth at consumer prices. Apple M3 Ultra ships up to 512 GB of unified LPDDR5X at 819 GB/s, accessible to both CPU and GPU with no PCIe transfer at all. Bandwidth-bound analytical operators — exactly the workloads OLAP engines were built for — should be a near-perfect match. The question is not whether the silicon is ready; it is whether anyone can find an organizational shape that survives building on it.

This paper presents gpudb, our attempt at that shape. We do not ship a new database. We ship a DuckDB extension that adds GPU-resident analytical operators behind a small, focused set of SQL functions: gpu_sum, gpu_min, gpu_max, hash GROUP BY, hash-join probe, and the beginnings of window-function support. The implementation has two production backends sharing one C++ codebase: NVIDIA CUDA, and Apple Silicon Metal. The Metal backend is, to our knowledge, the first published SQL execution engine to target Apple GPUs.

The contributions of this paper are:

  1. A four-layer architecture (extension → operator dispatch → abstract backend API → CPU/CUDA/Metal kernels) that lets a single binary carry one or more GPU backends and degrades cleanly to CPU when none is present at runtime.
  2. A set of Metal kernels that exploit Apple Silicon's unified memory architecture (UMA) to eliminate host↔device transfer entirely, and reach 87% of LPDDR5X peak bandwidth on multi-aggregate fusion (SELECT SUM(x), MIN(x), MAX(x), COUNT(x) FROM t) at 1 billion int64 rows.
  3. A cardinality-aware hybrid CPU/GPU planner that dispatches between a 32K-partition slot-lock hash aggregate (sweet spot 1024 ≤ unique ≤ 16M) and an optimized multi-pass radix sort, with a CPU fallback at the extremes where the GPU loses structurally.
  4. An algorithm playbook for SQL window functions on GPU — ROW_NUMBER, RANK, DENSE_RANK, LAG/LEAD, PARTITION BY, sliding aggregates — that reduces each to (1) sort key/payload pairs, (2) walk the sorted output, (3) scatter back. This is the operator class that Sirius (UW-Madison + NVIDIA, CIDR 2026), the strongest current academic GPU OLAP engine, does not implement.
  5. An empirical evaluation on TPC-H SF1/SF10 lineitem and synthetic workloads at 50M-1B rows, on both an RTX 4090 Laptop (CUDA 13.0, sm_89) and an Apple M4 Max (16-core LPDDR5X at 546 GB/s peak).

The project is Apache-2.0 licensed and lives at github.com/singhpratech/duckdbgpumetaldbram. Since this paper was first published it has become an official DuckDB Community Extension (registry PRs #1898 and #2404, both merged) — INSTALL gpudb FROM community; works in any DuckDB ≥ 1.5.5, and the registry serves v0.3.0. See the Addendum for the v0.3.0 results.

2.1 GPU databases as standalone systems

Standalone GPU databases began with academic prototypes — Govindaraju et al.'s GPUTeraSort (SIGMOD 2006), Mark Harris's Thrust-based GPU joins, then HyPer and Ocelot in the early 2010s. The commercial wave that followed (MapD/OmniSci/HEAVY.AI from 2013, Kinetica from 2014, BlazingSQL from 2018, Brytlyt from 2017, Voltron Data from 2021) all faced the same three problems: (1) customers had to migrate data out of their existing data warehouses to run anything; (2) GPUs were expensive to provision and idle most of the time outside of the query window; (3) the "GPU > CPU" assumption baked into the planner was empirically wrong on a large class of analytical workloads, particularly low-cardinality aggregations where a 20-thread DDR5 CPU saturates memory bandwidth that the GPU cannot improve on after paying PCIe transit. By 2025 every one of those vendors had been acqui-hired, dormant, or pivoted away. HEAVY.AI → NVIDIA 2025; BlazingSQL last release 0.21 (April 2021); Voltron Data laid off ~50% of staff in September 2024; Brytlyt → Opensignal (June 2024); Kinetica pivoted to vector search in 2024. The pattern is so consistent that any 2026 GPU SQL engine has to start by explaining why it will not end up there.

2.2 GPU as an extension to an existing engine

The far more credible 2025-2026 pattern is to delegate GPU execution from inside an established CPU engine. Sirius (Yogatama et al., CIDR 2026) is a DuckDB extension that pushes analytical operators down into libcudf on CUDA. Sirius is CUDA-only and does not implement window functions. cuDF itself (the RAPIDS dataframe library) is CUDA-only. DataFusion-Comet (Apple's Spark accelerator donated to the Apache Software Foundation) delivers roughly 2× speedups on TPC-DS SF1000 by replacing Spark's JVM operators with native code, partially GPU-accelerated. None of these targets Apple Silicon GPUs.

The hybrid-planner problem (when to dispatch to GPU vs CPU based on runtime cardinality and residency) is genuinely open per Rosenfeld and Breß's ACM Computing Surveys 2022 survey and Cao's VLDB 2024 paper on GPU database characterization and optimization. Most published systems either always-GPU (Sirius, cuDF) or always-CPU with optional acceleration hooks; few attempt a principled cardinality-aware switch at execution time.

2.3 The Apple Silicon gap

Apple's unified memory architecture (UMA) eliminates the single biggest GPU-database tax: PCIe transfer. On M3 Ultra, both CPU and GPU read the same physical LPDDR5X pool at up to 819 GB/s, with no cudaMemcpy equivalent in the critical path. On M4 Max (this paper's primary Mac testbed), the same architecture runs at 546 GB/s. By comparison, a typical RTX 4090 + DDR5 desktop pays a 32 GB/s PCIe Gen5 x16 round-trip whenever data starts on the host. For workloads dominated by a single pass over a column — exactly what aggregation queries look like — UMA is a substantial structural advantage. No published SQL engine has been built to exploit it. gpudb is our attempt to close that gap.

3. System Architecture

The codebase is organized in four layers, top to bottom:

┌─────────────────────────────────────────────────────────────┐
│  src/extension/        DuckDB extension wrapper (optional)  │
├─────────────────────────────────────────────────────────────┤
│  src/operators/        operator-level dispatch              │
│                        (aggregate, hash-join, window)       │
├─────────────────────────────────────────────────────────────┤
│  src/include/gpu_backend.hpp    abstract Aggregator API     │
├─────────────────────────────────────────────────────────────┤
│  src/backends/cpu/     scalar + OpenMP                      │
│  src/backends/cuda/    CUDA kernels + host wrapper          │
│  src/backends/metal/   Metal kernels + host wrapper         │
└─────────────────────────────────────────────────────────────┘

Three design decisions deserve explanation.

The library is independent of DuckDB. libgpudb compiles to a standalone static library that can be linked into any C++ project. The DuckDB extension (src/extension/gpu_sum_extension.{cpp,hpp}) is a thin wrapper that registers gpu_sum, gpu_min, gpu_max as DuckDB aggregate functions, handles validity-mask plumbing for NULL semantics, and provides a batched-finalize path for GROUP BY queries. Decoupling the library lets us iterate on kernels in seconds (the standalone build is ~5 seconds vs ~minutes for a full DuckDB rebuild) and verify numerical correctness without the SQL engine in the loop. It also leaves the door open to later embeddings — a Polars plug-in, a Python module, or a custom engine — without coupling those to DuckDB's lifecycle.

Backends are plug-in. Adding a new backend is one translation unit plus one entry in the dispatch factory. gpudb::default_backend() returns the best backend that both compiled into the binary and has a usable runtime device:

CUDA available?  → CUDA
else Metal?      → Metal
else             → CPU

This is also overridable at runtime via the GPUDB_FORCE_BACKEND environment variable, which is essential for cross-backend correctness testing and for the benchmark suite, which iterates over available_backends().

Memory model is backend-specific by design.

  • CUDA: explicit cudaMalloc + cudaMemcpyAsync over a single stream, with device-side buffer reuse (ensure_buffers) across calls. We do not use CUDA Unified Memory; experimentally it pessimizes our access patterns vs. explicit transfer in the resident-column case.
  • Metal: MTLBuffer with MTLResourceStorageModeShared. On Apple Silicon UMA this means there is no transfer cost — just a CPU-visible pointer that the GPU can read directly. The "cold" and "resident" columns of our benchmark tables therefore collapse into one on Metal.
  • CPU: zero-copy, obviously.

4. CUDA Backend

The CUDA backend ships SUM, MIN, MAX, GROUP BY hash aggregate, and a working hash-join probe.

4.1 Two-pass reduction

For SUM/MIN/MAX we use a two-pass reduction that matches the reference design across both CUDA and Metal:

Pass 1: per-block reduction
   blocks*BLOCK threads do grid-stride load → threadgroup memory
   intra-block tree reduction → 1 partial per block
Pass 2: single-block final reduction
   read partials → tree reduction → 1 scalar

This deliberately avoids CUB / Thrust in week-1 code — once more operators land we will swap to CUB for DeviceReduce::Sum, DeviceScan::ExclusiveSum, and DeviceRadixSort, all of which the planned operators need. On a 100M int64 column resident on an RTX 4090 Laptop, the kernel runs at 1187 GiB/s of effective bandwidth (0.04 ms wall time), or 17.9× the 20-thread CPU baseline of 54 GiB/s.

4.2 Open-addressing hash GROUP BY

The CUDA GROUP BY kernel uses an open-addressing hash table backed by 64-bit atomic compare-and-swap. The probing scheme is linear with a Murmur-style key mixer. Sized to roughly 2× the upper bound on unique keys, occupancy stays under 50% and probe chains stay short. At 50M rows × 1M unique groups on an RTX 4090 Laptop, the kernel runs at approximately 520 GiB/s of effective bandwidth and is 9.6× faster than a 20-thread parallel CPU hash GROUP BY end-to-end (130 ms vs. 1067 ms). At 50M rows × 10M unique groups the speedup widens to 13.7× (188 ms vs. 2321 ms), and the kernel-only ratio reaches roughly 50× — the gap is mostly PCIe transfer in the end-to-end measurement.

4.3 Hash join probe

The CUDA hash-join probe extends the same atomic-CAS hash-table primitive to the build side, then issues a per-row gather on the probe side. At 1M build × 10M probe with 97% selectivity on int64 keys, the join runs 3.7× faster wall-clock than the CPU baseline and 107× faster on the kernel alone. Joins dominate query time on most TPC-H queries, so even a 3-4× wall-clock win is the largest available impact-per-operator we can land.

5. Metal Backend on Apple Silicon

The Metal backend is the artifact this project exists to make. Three implementation decisions are specific to Apple Silicon and worth describing in detail.

5.1 Exploiting UMA

Every MTLBuffer we allocate uses MTLResourceStorageModeShared. Allocation returns a pointer the CPU can fill via standard memcpy (or skip the copy entirely if the data was already read into that pointer by the caller — for example, DuckDB's columnar buffers can be wrapped in place). The GPU then reads from the same backing pages. There is no blitEncoder transfer, no host-stage buffer, no double allocation. This is the difference that makes Apple Silicon worth targeting: a 1B-row int64 column requires 8 GB of LPDDR5X, and on M4 Max the SUM operator finishes in 16.2 ms (~470 GiB/s) on a column that was just produced by the CPU — no data movement at all in the critical path.

5.2 Multi-aggregate fusion

A common analytical query shape is "give me four numbers about this column": SELECT SUM(x), MIN(x), MAX(x), COUNT(x) FROM t. On a CPU engine, each of those is a separate pass over the column (or a vectorized fused micro-loop that still reads each cache line for each aggregate). On Metal we fuse all four into a single kernel that reads the column exactly once and updates four threadgroup accumulators in lockstep. On TPC-H SF10 l_extendedprice (~60M rows) the fused pass runs at ~475 GiB/s, which is 87% of M4 Max's LPDDR5X peak. The end-to-end SF10 multi-agg fusion speedup over a 16-thread DuckDB CLI baseline is:

TPC-H SF10 multi-agg fusionDuckDB CPU (16 threads)gpudb Metal v0.1.3Speedup
l_quantity27 ms1.06 ms25.5×
l_extendedprice26 ms1.18 ms22.0×
l_orderkey11 ms1.13 ms9.7×

The asymmetry between the columns reflects how much each can be pruned at filter time. Multi-aggregate fusion is also where the hardware ceiling becomes visible: on the l_extendedprice case at 87% of LPDDR5X peak, there is essentially no software headroom left for further single-column-pass speedups.

5.3 Hybrid GROUP BY: slot-lock partitions vs radix sort

The Metal GROUP BY is the most algorithm-heavy kernel in the project. Apple GPUs lack the well-tested 64-bit atomic CAS path we use on CUDA (the support story across Metal versions and OS releases is uneven). We therefore ship two GROUP BY implementations and auto-dispatch between them based on the input's expected unique-key count:

  • 32K-partition slot-lock hash aggregate: partitions the input by a hash of the key into 32K buckets, then within each bucket uses 1024 slots with 32-bit slot locks (which Metal supports universally) for the open-addressing probe. Capacity per partition is 1024 slots × 32K partitions = ~33.5M, with a Poisson tail at TPC-H SF10's 15M-unique-key workload of ~458 mean / ~543 worst per-partition occupancy — comfortably inside the 1024 budget. This is the path that wins between roughly 1024 and 16M unique keys.
  • Multi-pass radix sort: sorts the key column by 8-bit digits, then reduces by key. This wins at very high cardinality (where the hash table thrashes) and at very low cardinality (where the partitioning overhead is wasted, and even a 1B-row sort runs in well under a second at LPDDR5X peak).

At runtime, the dispatch decision is based on a cardinality estimate (NDV — distinct-value count) derived from a small sample of the input. End-to-end on TPC-H SF10 GROUP BY l_extendedprice (~1.35M unique on 60M rows), the slot-lock path runs in 28.9 ms vs. DuckDB CPU's 113 ms — a 3.9× speedup. At 50M-1B rows × 1M synthetic GROUP BY keys, the speedup over the 16-thread DuckDB baseline is consistently 3-4×:

Synthetic GROUP BYDuckDB CPUgpudb MetalSpeedup
100M × 1M124 ms47 ms2.6×
500M × 1M820 ms242 ms3.4×
1B × 1M2500 ms770 ms3.2×

The honest opposite case is at very low cardinality. SELECT l_quantity, COUNT(*) FROM lineitem_sf10 GROUP BY l_quantity has only 50 distinct values across 60M rows. Here the CPU's parallel hash map saturates DDR5 in 26 ms while our slot-lock partition path takes 372 ms — a structural 14× loss. The hybrid planner (see Section 6) is the mechanism that prevents that from happening at query time.

6. The Hybrid CPU/GPU Planner

The single biggest failure mode of historical GPU databases was assuming GPU was always faster than CPU. The empirical reality, visible in our benchmark tables and confirmed independently by Rosenfeld & Breß (CSUR 2022) and Cao (VLDB 2024), is that GPU wins decisively on memory-latency-bound work (random hash probes, large NDV aggregations) and loses on memory-bandwidth-bound work that the CPU's prefetcher already saturates (small NDV aggregations, short scans on hot caches).

The HybridAggregator in src/operators/group_by_aggregate.cpp wraps a DispatchDecision that takes the following inputs:

  1. Input cardinality (row count n): below ~50K rows, GPU launch overhead alone exceeds the CPU's wall time. Stay on CPU.
  2. Estimated NDV: if known (e.g., from a histogram), use it directly. If not, take a 64K-row sample and run HyperLogLog on it. Below ~64 unique keys, dispatch to CPU; above, dispatch to GPU.
  3. Residency: if the column is already in a Metal MTLBuffer (because a prior operator left it there), the transfer cost is zero and we lower the GPU threshold by ~3×.
  4. Operator shape: multi-aggregate fusion always dispatches to GPU when n is large enough — the 22-25× speedups are not closely matched by any CPU path.

The decision is made at FinalizeInput time, so it has access to the actual input chunk and not just the optimizer's planning-time estimates. This matters in practice because DuckDB's optimizer may misestimate NDV by an order of magnitude on intermediate aggregations.

7. Window Functions on GPU

SQL window functions (ROW_NUMBER() OVER (ORDER BY k), RANK, LAG, SUM ... OVER (PARTITION BY p ORDER BY k)) are the operator class that Sirius (CIDR 2026) does not implement. They are also high-value in analytical workloads — most TPC-DS queries and most real-world BI dashboards depend on them. Shipping them is a defensible differentiator.

The key observation is that once you have sorted (key, payload) pairs, every window function reduces to (1) sort, (2) walk the sorted output with a per-element rule, (3) scatter the result back to original positions. The hard part is the data movement — the actual window operator is mostly bookkeeping. We already have radix-sort kernels for GROUP BY; refactored into a generic radix_sort_pairs_i64 they become the workhorse for the entire window-function family.

The mapping for the operators we plan to ship is:

Window functionGPU recipeApprox. cost vs ROW_NUMBER
ROW_NUMBER() OVER (ORDER BY k)Sort (key, original_index) pairs; scatter output[orig] = r + 1.1.0×
RANK() OVER (ORDER BY k)Sort + emit tie marker (key[r] != key[r-1]) + inclusive prefix sum + scatter.~1.5×
DENSE_RANKSame as RANK with a different scan interpretation.~1.5×
LAG(v, k) / LEAD(v, k)Sort (key, orig, value) triples; shifted read lag_value[r] = sorted_value[r-k]; scatter.~1.1×
PARTITION BY p ORDER BY kSort composite (p << 32 | k); detect partition boundaries via tie scan; subtract partition starts for inner rank.~3×
SUM ... OVER (ORDER BY k ROWS BETWEEN n PRECEDING AND CURRENT)Sort + inclusive scan of values + difference cum[r] - cum[r-n-1] + scatter.~2×

None of these require operators that we do not already have or can extract from existing kernels. radix_per_bucket_scan (already shipped for GROUP BY) generalizes to a flat exclusive_scan_i64 with ~50 lines of factoring. The window-function set will land in 4-5 PRs of focused work in v0.2.x.

The differentiator pitch when this is done: "We are a DuckDB extension with first-class Apple Silicon support. We do GROUP BY 3-9× faster than a 16-thread CPU at TPC-H SF10 scales, SUM at 22-25× via multi-aggregate fusion, and we ship window functions, which the strongest competing GPU OLAP engine does not. Same SQL surface; drop in via LOAD gpudb;."

8. Evaluation

8.1 Hardware

  • NVIDIA testbed: RTX 4090 Laptop (Ada Lovelace, sm_89, 16 GB GDDR6X at 1008 GB/s peak), Intel mobile CPU with 20 hardware threads, DDR5 system memory, PCIe Gen5 x16. CUDA 13.0.
  • Apple testbed: M4 Max (12 P-cores + 4 E-cores = 16 threads, 40-core integrated GPU, 64 GB LPDDR5X at 546 GB/s peak). macOS 14, Xcode 16 Metal toolchain. The DuckDB CLI uses 16 threads by default on M4 Max, matching the baseline we benchmark against.

8.2 Workloads

  • TPC-H SF1 + SF10 lineitem: 6M and 60M row scale factors of the standard TPC-H lineitem table, generated via the DuckDB CLI's tpch extension. Queries: per-column SUM, MIN, MAX (hot and cold), multi-aggregate fusion, GROUP BY l_orderkey (1.5M / 15M unique), GROUP BY l_quantity (50 unique — the low-cardinality structural-loss case), GROUP BY l_extendedprice (1.35M unique).
  • Synthetic GROUP BY at billion-row scale: int64 keys uniformly distributed across N unique values, varied at 1M and 10M unique with row counts of 100M, 500M, 1B.
  • Hash join: 1M build × 10M probe at 97% selectivity (a high-selectivity case where the join's output is itself a substantial data movement).
  • Resident-column microbenchmarks: SUM/MIN/MAX over a column held in device memory across calls, isolating the kernel from any transfer cost.

8.3 RTX 4090 results

WorkloadCPU baselineCUDA coldCUDA residentSpeedup
SUM 100M int6413.8 ms / 54 GiB/s80.6 ms (PCIe-bound)0.04 ms / 1187 GiB/s17.9× over CPU
SUM 6M TPC-H lineitem l_orderkey0.7 ms / 65 GiB/s4.9 ms0.04 ms / 1187 GiB/s17.9× over CPU
GROUP BY 50M × 1M groups1067 ms (serial)130 msn/a9.6×
GROUP BY 50M × 10M groups2321 ms188 msn/a13.7×
GROUP BY 6M TPC-H lineitem (1.5M groups)54.1 ms15.0 msn/a3.6×
Hash join 1M × 10M @ 97% sel3.7× wall / 107× kernel

The resident-column SUM at 1187 GiB/s exceeds the device's nominal 1008 GB/s peak GDDR6X bandwidth; this reflects effective bandwidth measured at the kernel boundary (the reduction tree's per-block partials live in L2 / shared memory and never round-trip to GDDR6X for the final pass). The cold-column SUM at 80.6 ms is dominated by PCIe transfer; this is the case where a hybrid planner has to know not to dispatch to GPU.

8.4 Apple M4 Max results

The M4 Max numbers are vs duckdb -c "SET threads=16", the actual user-visible default on this chip. The DuckDB CLI uses 12 P-cores + 4 E-cores by default; we baseline against that, not against a single-threaded reference.

WorkloadDuckDB CPU (16 threads)gpudb Metal v0.1.3Speedup
TPC-H SF10 multi-agg fusion l_quantity27 ms1.06 ms25.5×
TPC-H SF10 multi-agg fusion l_extendedprice26 ms1.18 ms22.0×
TPC-H SF10 multi-agg fusion l_orderkey11 ms1.13 ms9.7×
SF10 SUM l_quantity hot5 ms1.16 ms4.3×
TPC-H SF10 GROUP BY l_extendedprice (1.35M unique)113 ms28.9 ms3.9×
500M × 1M synthetic GROUP BY820 ms242 ms3.4×
1B × 1M synthetic GROUP BY2500 ms770 ms3.2×
1B int64 SUM hot40 ms16.2 ms2.6×
SF10 SUM l_extendedprice hot5 ms2.23 ms2.2×
SF10 SUM l_orderkey hot3 ms1.68 ms1.8×
TPC-H SF1 GROUP BY l_orderkey (1.5M unique)8 ms5.71 ms1.4×
TPC-H SF10 GROUP BY l_orderkey (15M unique)56 ms42.95 ms1.3×
TPC-H SF10 GROUP BY l_quantity (50 unique)26 ms372 ms0.07× (CPU 14× faster)

The last row is the structural-loss case described in Section 5.3. At 50 unique keys across 60M rows, a 16-thread parallel hash-map saturates the CPU's bandwidth so completely that no GPU strategy we have implemented matches it. This is exactly the case the hybrid planner intercepts and routes to the CPU; we report the GPU-only number for transparency.

8.5 SQL test suite

The repository's test/sql/*.test directory contains 5 files / 46 queries covering gpu_sum, gpu_min, gpu_max, NULL semantics, GROUP BY edge cases, and window-function regressions. As of v0.1.3, 46 of 46 pass with 0 expected-fail. The four OVER () / running-sum / partitioned-window / mid-cardinality GROUP BY bugs flagged in earlier KNOWN_ISSUES.md snapshots were all closed by PRs #18, #20, #21, and #22.

9. Limitations and Known Divergences

Three behaviors deserve explicit disclosure. They are not bugs — they are documented v0.1.x design choices that will be revisited.

  1. SUM/MIN/MAX on empty input or all-NULL input returns 0 instead of SQL NULL. Returning 0 is the C++ aggregator's natural behavior; properly returning NULL requires plumbing duckdb_validity_set_row_invalid on the output vector, which the v0.1.x extension wrapper does not yet do. SQL queries that test for IS NULL on a fully-empty aggregate result will see false where DuckDB native would return NULL. Tracked for v0.2.0.
  2. gpu_sum(DOUBLE) falls back to host on Apple Silicon. Apple GPUs do not implement IEEE-754 double precision in MSL. The result is numerically correct but uses the CPU path with no GPU acceleration. This is a hardware constraint and will not be fixed inside gpudb; the planner correctly avoids dispatching DOUBLE aggregates to Metal.
  3. Metal hash join is currently a CPU-fallback scaffold. CUDA hash-join is real (Section 4.3); Metal hash-join is a scaffold that delegates to CPU until a sort-merge join path lands in v0.2.x. The intended Metal path is sort-then-merge, not hash, given the absence of a robust 64-bit atomic CAS story across Apple Silicon's MSL versions.

Three further v0.2.0+ items are explicitly on the roadmap but not yet implemented: GPU-resident segment-reduce for Metal GROUP BY at 1B+ rows (the cell where we currently lose 1.5× at 15M unique on SF10 l_orderkey); resident-column SQL hooks (a gpu_cache(table, col) table function so gpu_sum can run on data already loaded into device memory across queries); and string / regex operators (where libcudf-class functionality on Metal does not currently exist anywhere).

10. Discussion

10.1 Why a DuckDB extension and not a new database

Sections 1 and 2.1 lay out the historical evidence that standalone GPU databases as a commercial category have collapsed. The deeper structural reason is that they ask customers to do something every customer should rationally refuse to do: migrate analytical data out of a system that already works — Snowflake, BigQuery, Databricks, ClickHouse, or now DuckDB itself — and into a separate one that comes with a vendor lifecycle risk. The DuckDB-extension shape collapses that ask to LOAD gpudb. There is no migration. The existing query surface still works. If the extension is unloaded the engine continues to function. This is the same architectural shape that DuckDB VSS and Sirius take, and the same shape we believe will define the next generation of analytical-acceleration projects.

10.2 Why Apple Silicon

The narrow reason is Section 2.3: no published SQL engine targets Apple GPUs as of May 2026, and UMA is a genuine structural advantage for column-pass workloads. The wider reason is that Apple Silicon is the only widely-deployed consumer hardware in 2026 where a 64 GB device with hundreds of GB/s of memory bandwidth costs less than a single H100 and runs on every developer's laptop. This makes the development surface enormous. A meaningful fraction of the people who would otherwise have to provision cloud GPU time to run an analytical accelerator could run gpudb's Metal backend on their existing MacBook Pro, on data they already have locally. The viability of this category depends on that accessibility.

10.3 What the hybrid planner says about the rest of the field

Every benchmark table in this paper that includes a "loss" cell (the 50-unique-key GROUP BY on M4 Max; the 80.6 ms cold-column SUM on RTX 4090) is also an indictment of the most common GPU-database design pattern: dispatch everything to GPU and hope the average wins. The empirical reality is that the per-query distribution has a long left tail where the CPU wins decisively, and a few of those queries embedded in a workload are sufficient to convince the user that "GPU acceleration" is unreliable. A hybrid planner that knows which side to dispatch each query to — rather than averaging across the workload — is, in our view, the single architectural change with the most upside left in 2026 GPU OLAP. We have built one inside gpudb and report its dispatch decisions transparently. We do not yet ship machine-learning-based dispatch (Cao VLDB 2024 explores this); empirical thresholds documented in BENCHMARK.md have so far been sufficient.

10.4 What is genuinely hard about this project

Three things, in order of nastiness:

  • DuckDB's window operator's CONSTANT_VECTOR state. DuckDB's window machinery passes aggregates a CONSTANT_VECTOR-shaped state for unbounded-frame queries (SUM(v) OVER ()), which our buffer-pool-based aggregator state did not initially handle. PR #22 fixes this with a magic-word probe inside update() and a POD state, which is the only honest way we found to defend against the upstream API's polymorphic state shape. This took two passes to get right; the first attempt missed a SIGSEGV that only fired in a multi-chunk window workload.
  • Metal 64-bit atomic CAS support. The story across Metal versions and OS releases is inconsistent enough that we cannot rely on it; the slot-lock hash aggregate in Section 5.3 is the workaround. This costs us a factor of 1.3-1.5× at very high (15M+) unique-key cardinality on M4 Max where a real device-side hash table would win cleanly.
  • The absence of CUB on Metal. CUDA's CUB library provides battle-tested device-side primitives (radix sort, scan, reduce) that we can compose without writing kernel code. Metal has no equivalent. Every primitive in the Metal path is hand-written. The radix sort kernels in groupby.metal are the most engineering-heavy part of the project.

11. Conclusion

gpudb ships analytical operators on both NVIDIA CUDA and Apple Silicon Metal from a single C++ codebase, behind a DuckDB extension that requires no migration. On TPC-H SF10 lineitem queries on M4 Max it delivers 22-25× speedups for multi-aggregate fusion, 3-4× speedups for GROUP BY at the 1M-unique-key sweet spot, and 1.3-4× speedups across the rest of the lineitem aggregate shape. On RTX 4090 it delivers 9-13× for GROUP BY at high cardinality and 17× for resident-column SUM. A hybrid CPU/GPU planner detects the workloads where GPU dispatch is unprofitable (notably very low cardinality and short, hot scans) and routes them to the CPU. Window functions — the operator class missing from the strongest CUDA-only competitor — have a complete algorithm playbook and partial v0.1.x scaffolding, with the full implementation targeted for v0.2.x.

The wider claim this paper makes is structural rather than performance-numeric. The 2013-2024 commercial GPU-database wave collapsed because it asked customers to migrate. The next wave, if there is one, will succeed only by attaching to engines those customers already use. DuckDB is the most credible attachment point in 2026; Apple Silicon is the most under-served substrate; the gap between them is the wedge this project is built to occupy.

Addendum — Status Update (August 2026)

This section was added on 2026-08-11 and reports on releases v0.2.0 and v0.3.0 (both 2026-07-19). The evaluation in §8 is left as originally published; all numbers there remain the v0.1.3 record.

A.1 End-to-end parity via streaming aggregate states (v0.3.0)

v0.2.0 added SQL-correct NULL semantics and DOUBLE overloads, and recorded an honest end-to-end result: on rewritten TPC-H queries through the DuckDB CLI, the extension's aggregate path lost to native DuckDB by 3×–109×. The cause was structural — the path buffered every value into per-state vectors before reducing, and on unified memory that copy is the entire cost.

v0.3.0 rewrote the aggregate path to streaming running accumulators, the same algorithmic shape as a native DuckDB aggregate. On the identical suite (Apple M4 Max, DuckDB CLI, 16 threads, median of 5):

QueryScalenativev0.3.0ratiov0.2.0 ratio
Q6SF10.002 s0.002 s1.00×~3×
Q6SF100.017 s0.018 s1.06×~3.4×
Q1SF10.011 s0.012 s1.09×~30×
Q1SF100.098 s0.101 s1.03×~35×
GROUP BY l_orderkeySF10.010 s0.012 s1.20×~86×
GROUP BY l_orderkeySF100.092 s0.109 s1.18×~109×

Every cell is now within 0–20% of native, with identical results (Q6 totals match to the printed digit; Q1 row sets match row-for-row; GROUP BY checksums identical at both scale factors). The SF10 GROUP BY cell alone improved from 11.05 s to 0.109 s. On unified memory the streamed path no longer dispatches scalar aggregates to the GPU at all — shipping a column to a coprocessor that shares your memory in order to sum it is pure overhead. GPU value on the SQL path accordingly moves to compute-dense operators; device-level reductions (§4–§5) are unaffected.

A.2 Distribution: official DuckDB Community Extension

Community-extensions PR #1898 (acceptance, merged 2026-07-24) and PR #2404 (version bump, merged 2026-08-03) make gpudb installable in any DuckDB ≥ 1.5.5 via INSTALL gpudb FROM community; — signed binaries on four platforms, with the full Metal backend in the Apple Silicon build and a CPU fallback elsewhere. The CUDA backend still requires a source build on Linux; toolchain packaging is scheduled for v0.4.0.

A.3 In-flight: GPU joins

An externally contributed Metal hash join with an on-device segment reduce and a gpu_inner_join surface (repository PR #43) has been verified at 9.9× on a 1M × 10M inner join on M4 Max, and defines the v0.4.0 arc: joins are compute-dense in precisely the way scalar aggregates are not, making them the appropriate SQL-path GPU workload on unified memory.

A companion write-up of the v0.3.0 release is on the blog: The First SQL Engine for Apple Silicon GPUs Is Now a DuckDB Community Extension.

References

  1. Y. Yogatama, et al. Rethinking Analytical Processing in the GPU Era. CIDR 2026. PDF. [Sirius — UW-Madison + NVIDIA.]
  2. J. Cao. GPU Database Systems Characterization and Optimization. PVLDB Vol. 17, VLDB 2024. PDF.
  3. V. Rosenfeld, S. Breß, et al. Query Processing on Heterogeneous CPU/GPU Systems. ACM Computing Surveys 55(1), 2022. ACM DL.
  4. DuckDB Foundation. DuckDB Extensions API and the Community Extensions Registry. Docs. PR #1898 (gpudb submission): link.
  5. RAPIDS Team. cuDF — GPU DataFrames. Repo.
  6. Apache Software Foundation. DataFusion-Comet (Apple's Spark accelerator, donated to ASF). Repo.
  7. HEAVY.AI Wikipedia entry (history of MapD/OmniSci, NVIDIA acquisition 2025). Link.
  8. OpenSignal. OpenSignal acquires Brytlyt. June 2024. Press release.
  9. The Information. AI Startup Voltron Data Switches CEOs, Lays Off Staff. September 2024. Article.
  10. Apple. M3 Ultra and M4 Max hardware specifications. Apple Newsroom.
  11. Source code, benchmarks, and reproducibility scripts: github.com/singhpratech/duckdbgpumetaldbram (Apache-2.0).

Reproducibility entry point: scripts/local_check.sh in the repository runs the full pipeline end-to-end (configure → build → 77 C++ unit tests → smoke benchmarks → 46-query SQL suite). The CI workflow at .github/workflows/ci.yml.disabled is staged for re-enable; the project currently relies on local validation against the RTX 4090 + M4 Max dev fleet.